001package jmri.jmrit.progsupport;
002
003import java.awt.event.ActionListener;
004import java.beans.PropertyChangeListener;
005import java.util.ArrayList;
006import java.util.HashMap;
007import java.util.List;
008import javax.annotation.Nonnull;
009import javax.swing.BoxLayout;
010import javax.swing.ButtonGroup;
011import javax.swing.JComboBox;
012import javax.swing.JRadioButton;
013import jmri.GlobalProgrammerManager;
014import jmri.InstanceManager;
015import jmri.Programmer;
016import jmri.ProgrammingMode;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * Provide a JPanel to configure the service mode (Global) programmer.
022 * <p>
023 * The using code should get a configured programmer with getProgrammer. Since
024 * there's only one service mode programmer, maybe this isn't critical, but it's
025 * a good idea for the future.
026 * <p>
027 * A ProgModePane may "share" between one of these and a ProgOpsModePane, which
028 * means that there might be _none_ of these buttons selected. When that
029 * happens, the mode of the underlying programmer is left unchanged and no
030 * message is propagated.
031 * <p>
032 * Note that you should call the dispose() method when you're really done, so
033 * that a ProgModePane object can disconnect its listeners.
034 * <hr>
035 * This file is part of JMRI.
036 * <p>
037 * JMRI is free software; you can redistribute it and/or modify it under the
038 * terms of version 2 of the GNU General Public License as published by the Free
039 * Software Foundation. See the "COPYING" file for a copy of this license.
040 * <p>
041 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
042 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
043 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
044 *
045 * @author Bob Jacobsen      Copyright (C) 2001, 2014
046 * @author Daniel Bergqvist  Copyright (C) 2021
047 */
048public class ProgServiceModePane extends ProgModeSelector implements PropertyChangeListener, ActionListener {
049
050    ButtonGroup modeGroup = new ButtonGroup();
051    HashMap<ProgrammingMode, JRadioButton> buttonMap = new HashMap<>();
052    JComboBox<GlobalProgrammerManager> progBox;
053    ArrayList<JRadioButton> buttonPool = new ArrayList<>();
054
055    /**
056     * Get the selected programmer
057     */
058    @Override
059    public Programmer getProgrammer() {
060        if (progBox.getSelectedItem() == null) {
061            return null;
062        }
063        return ((GlobalProgrammerManager) progBox.getSelectedItem()).getGlobalProgrammer();
064    }
065
066    /**
067     * Are any of the modes selected?
068     *
069     * @return true is any button is selected
070     */
071    @Override
072    public boolean isSelected() {
073        for (JRadioButton button : buttonMap.values()) {
074            if (button.isSelected()) {
075                return true;
076            }
077        }
078        return false;
079    }
080
081    /**
082     * @param direction controls layout, either BoxLayout.X_AXIS or
083     *                  BoxLayout.Y_AXIS
084     */
085    public ProgServiceModePane(int direction) {
086        this(direction, new javax.swing.ButtonGroup());
087    }
088
089    /**
090     * Get the list of global managers.
091     *
092     * @return empty list if none
093     */
094    @Nonnull
095    public List<GlobalProgrammerManager> getMgrList() {
096        return InstanceManager.getList(jmri.GlobalProgrammerManager.class);
097    }
098
099    /**
100     * Create a new Programmer Service Mode Pane.
101     * @param direction controls layout, either BoxLayout.X_AXIS or
102     *                  BoxLayout.Y_AXIS
103     * @param group     mode button group.
104     */
105    public ProgServiceModePane(int direction, javax.swing.ButtonGroup group) {
106        modeGroup = group;
107
108        // general GUI config
109        setLayout(new BoxLayout(this, direction));
110
111        // create the programmer display combo box
112        java.util.List<GlobalProgrammerManager> v = new java.util.ArrayList<>();
113        for (GlobalProgrammerManager pm : getMgrList()) {
114            Programmer progrmr = pm.getGlobalProgrammer();
115            if (progrmr!=null) {
116                v.add(pm);
117                // listen for changes
118                progrmr.addPropertyChangeListener(this);
119            }
120        }
121
122        add(progBox = new JComboBox<>(v.toArray(new GlobalProgrammerManager[0])));
123        // if only one, don't show
124        if (progBox.getItemCount() < 2) {
125            // no choice, so don't display, don't monitor for changes
126            progBox.setVisible(false);
127        } else {
128            log.debug("Set combobox box selection to InstanceManager global default: {}", InstanceManager.getDefault(jmri.GlobalProgrammerManager.class));
129            progBox.setSelectedItem(InstanceManager.getDefault(jmri.GlobalProgrammerManager.class)); // set default
130            progBox.addActionListener((java.awt.event.ActionEvent e) -> {
131                // new programmer selection
132                programmerSelected();
133            });
134        }
135
136        // Horizontal glue is needed since the panel is too narrow otherwise
137        add(javax.swing.Box.createHorizontalGlue());
138
139        // and execute the setup for 1st time
140        programmerSelected();
141
142    }
143
144    /**
145     * reload the interface with the new programmers
146     */
147    void programmerSelected() {
148        log.debug("programmerSelected starts with {} buttons", buttonPool.size());
149        // hide buttons
150        for (JRadioButton button : buttonPool) {
151            button.setVisible(false);
152        }
153
154        // clear map
155        buttonMap.clear();
156
157        // configure buttons
158        int index = 0;
159        if (getProgrammer() == null) {
160            return;
161        }
162        List<ProgrammingMode> modes = getProgrammer().getSupportedModes();
163        log.debug("   has {} modes", modes.size());
164        for (ProgrammingMode mode : modes) {
165            JRadioButton button;
166            // need a new button?
167            if (index >= buttonPool.size()) {
168                log.debug("   add button");
169                button = new JRadioButton();
170                buttonPool.add(button);
171                modeGroup.add(button);
172                button.addActionListener(this);
173                add(button); // add to GUI
174            }
175            // configure next button in pool
176            log.debug("   set for {}", mode.toString());
177            button = buttonPool.get(index++);
178            button.setVisible(true);
179            modeGroup.add(button);
180            button.setText(mode.toString());
181            buttonMap.put(mode, button);
182        }
183        setGuiFromProgrammer();
184    }
185
186    /**
187     * Listen to buttons for mode changes
188     */
189    @Override
190    public void actionPerformed(@Nonnull java.awt.event.ActionEvent e) {
191        // find selected button
192        log.debug("Selected button: {}", e.getActionCommand());
193        for (ProgrammingMode mode : buttonMap.keySet()) {
194            if (mode.toString().equals(e.getActionCommand())) {
195                log.debug("      set mode {} on {}", mode.toString(), getProgrammer());
196                getProgrammer().setMode(mode);
197                return; // 1st match
198            }
199        }
200    }
201
202    /**
203     * Listen to programmer for mode changes
204     */
205    @Override
206    public void propertyChange(@Nonnull java.beans.PropertyChangeEvent e) {
207        if ("Mode".equals(e.getPropertyName()) && getProgrammer().equals(e.getSource())) {
208            // mode changed in programmer, change GUI here if needed
209            log.debug("Mode propertyChange with {}", isSelected());
210            if (isSelected()) {  // only change mode if we have a selected mode, in case some other selector with shared group has the selection
211                setGuiFromProgrammer();
212            }
213        }
214    }
215
216    void setGuiFromProgrammer() {
217        ProgrammingMode mode = getProgrammer().getMode();
218        JRadioButton button = buttonMap.get(mode);
219        log.debug("  setting button for mode {}", mode);
220        if (button == null) {
221            log.debug("   didn't find button, returning");
222            return;
223        }
224        button.setSelected(true);
225    }
226
227    // no longer needed, disconnect if still connected
228    @Override
229    public void dispose() {
230        for (GlobalProgrammerManager pm : getMgrList()) {
231            Programmer gp = pm.getGlobalProgrammer();
232            if (gp != null) {
233                gp.removePropertyChangeListener(this);
234            }
235        }
236    }
237
238    private final static Logger log = LoggerFactory.getLogger(ProgServiceModePane.class);
239}