001package jmri.jmrit.beantable.usermessagepreferences;
002
003import java.awt.BorderLayout;
004import java.awt.Dimension;
005import java.awt.event.ActionEvent;
006import java.beans.PropertyChangeEvent;
007import java.util.ArrayList;
008import java.util.HashMap;
009import java.util.Map.Entry;
010import javax.swing.BorderFactory;
011import javax.swing.BoxLayout;
012import javax.swing.JCheckBox;
013import javax.swing.JComboBox;
014import javax.swing.JComponent;
015import javax.swing.JLabel;
016import javax.swing.JPanel;
017import javax.swing.JScrollPane;
018import javax.swing.JTabbedPane;
019import jmri.UserPreferencesManager;
020import jmri.jmrit.beantable.AudioTableAction;
021import jmri.jmrit.beantable.BlockTableAction;
022import jmri.jmrit.beantable.IdTagTableAction;
023import jmri.jmrit.beantable.LRouteTableAction;
024import jmri.jmrit.beantable.LightTableAction;
025import jmri.jmrit.beantable.LogixTableAction;
026import jmri.jmrit.beantable.MemoryTableAction;
027import jmri.jmrit.beantable.ReporterTableAction;
028import jmri.jmrit.beantable.RouteTableAction;
029import jmri.jmrit.beantable.SectionTableAction;
030import jmri.jmrit.beantable.SensorTableAction;
031import jmri.jmrit.beantable.SignalGroupTableAction;
032import jmri.jmrit.beantable.SignalHeadTableAction;
033import jmri.jmrit.beantable.SignalMastTableAction;
034import jmri.jmrit.beantable.TransitTableAction;
035import jmri.jmrit.beantable.TurnoutTableAction;
036import jmri.jmrit.logixng.LogixNG_UserPreferences;
037import jmri.swing.PreferencesPanel;
038import jmri.util.swing.JmriPanel;
039import org.openide.util.lookup.ServiceProvider;
040
041/**
042 * Pane to show User Message Preferences.
043 *
044 * <p>Class based preferences are returned from {@link jmri.managers.JmriUserPreferencesManager}.
045 * If a class has more than one possible message, it will have its own tab.</p>
046 *
047 * <p>Classes with a single possible message will be in the <b>Misc items</b> tab.  These are only
048 * shown when active.</p>
049 *
050 * <p>The <b>Misc items</b> tab includes an option to show only tabs that have active messages. The
051 * normal tabs list is quite long.</p>
052 *
053 * @author Kevin Dickerson Copyright (C) 2009
054 */
055@ServiceProvider(service = PreferencesPanel.class)
056public class UserMessagePreferencesPane extends JmriPanel implements PreferencesPanel {
057
058    UserPreferencesManager p;
059
060    JTabbedPane tab = new JTabbedPane();
061
062    private HashMap<JComboBox<Object>, ListItems> _comboBoxes = new HashMap<>();
063    private HashMap<JCheckBox, ListItems> _checkBoxes = new HashMap<>();
064
065    private JCheckBox tabsOption = new JCheckBox(Bundle.getMessage("ShowTabsCheckBox"));
066
067    public UserMessagePreferencesPane() {
068        super();
069        p = jmri.InstanceManager.getDefault(UserPreferencesManager.class);
070        p.addPropertyChangeListener((PropertyChangeEvent e) -> {
071            if (e.getPropertyName().equals(UserPreferencesManager.PREFERENCES_UPDATED)) {
072                refreshOptions();
073            }
074        });
075
076        tabsOption.setSelected(p.getCheckboxPreferenceState(getClassName() + ".tabsOption", false));
077        tabsOption.addActionListener((ActionEvent e) -> {
078            p.setCheckboxPreferenceState(getClassName() + ".tabsOption", tabsOption.isSelected());
079        });
080
081        if (!tabsOption.isSelected()) {
082            setMinimumMessagePref();
083        }
084
085        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
086        newMessageTab();
087        add(tab);
088    }
089
090    private synchronized void setMinimumMessagePref() {
091        //This ensures that as a minimum that the following items are at least initialised and appear in the preference panel
092        p.setClassDescription(AudioTableAction.class.getName());
093        p.setClassDescription(BlockTableAction.class.getName());
094        p.setClassDescription(IdTagTableAction.class.getName());
095
096        p.setClassDescription(LightTableAction.class.getName());
097        p.setClassDescription(LogixTableAction.class.getName());
098        p.setClassDescription(LRouteTableAction.class.getName());
099        p.setClassDescription(MemoryTableAction.class.getName());
100
101        p.setClassDescription(ReporterTableAction.class.getName());
102        p.setClassDescription(RouteTableAction.class.getName());
103
104        p.setClassDescription(SectionTableAction.class.getName());
105        p.setClassDescription(SensorTableAction.class.getName());
106        p.setClassDescription(SignalGroupTableAction.class.getName());
107        p.setClassDescription(SignalHeadTableAction.class.getName());
108        p.setClassDescription(SignalMastTableAction.class.getName());
109
110        p.setClassDescription(TransitTableAction.class.getName());
111        p.setClassDescription(TurnoutTableAction.class.getName());
112
113        p.setClassDescription(LogixNG_UserPreferences.class.getName());
114    }
115
116    private synchronized void newMessageTab() {
117        remove(tab);
118        tab = new JTabbedPane();
119
120        //might need to redo this so that it doesn't recreate everything all the time.
121        _comboBoxes = new HashMap<>();
122        _checkBoxes = new HashMap<>();
123
124        ArrayList<String> preferenceClassList = p.getPreferencesClasses();
125        java.util.Collections.sort(preferenceClassList);
126        for (String strClass : preferenceClassList) {
127
128            JPanel classholder = new JPanel();
129            classholder.setLayout(new BorderLayout());
130
131            HashMap<Integer, String> options;
132            boolean add = false;
133            boolean addtoindependant = false;
134            if (p.getPreferencesSize(strClass) > 1) {
135                addtoindependant = true;
136            }
137            JPanel classPanel = new JPanel();
138            classPanel.setLayout(new BoxLayout(classPanel, BoxLayout.Y_AXIS));
139            classPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
140            for (int j = 0; j < p.getMultipleChoiceSize(strClass); j++) {
141                String itemName = p.getChoiceName(strClass, j);
142                options = p.getChoiceOptions(strClass, itemName);
143                if (options != null) {
144                    JComboBox<Object> optionBox = new JComboBox<>();
145                    ListItems li = new ListItems(strClass, itemName);
146                    _comboBoxes.put(optionBox, li);
147                    li.isIncluded(addtoindependant);
148                    optionBox.removeAllItems();
149                    for (Object value : options.values()) {
150                        optionBox.addItem(value);
151                    }
152                    int current = p.getMultipleChoiceOption(strClass, itemName);
153
154                    if (options.containsKey(current)) {
155                        optionBox.setSelectedItem(options.get(current));
156                    }
157                    if (addtoindependant) {
158                        JPanel optionPanel = new JPanel();
159                        JLabel _comboLabel = new JLabel(p.getChoiceDescription(strClass, itemName), JLabel.LEFT);
160                        _comboLabel.setAlignmentX(0.5f);
161                        optionPanel.add(_comboLabel);
162                        optionPanel.add(optionBox);
163                        add = true;
164                        classPanel.add(optionPanel);
165                    }
166                }
167            }
168            ArrayList<String> singleList = p.getPreferenceList(strClass);
169            if (!singleList.isEmpty()) {
170                for (int i = 0; i < singleList.size(); i++) {
171                    String itemName = p.getPreferenceItemName(strClass, i);
172                    String description = p.getPreferenceItemDescription(strClass, itemName);
173                    if ((description != null) && (!description.isEmpty())) {
174                        JCheckBox check = new JCheckBox(description);
175                        check.setSelected(p.getPreferenceState(strClass, itemName));
176                        ListItems li = new ListItems(strClass, itemName);
177                        _checkBoxes.put(check, li);
178                        li.isIncluded(addtoindependant);
179
180                        if (addtoindependant) {
181                            classPanel.add(check);
182                            add = true;
183                        }
184                    }
185                }
186            }
187            if (add) {
188                classholder.add(classPanel, BorderLayout.NORTH);
189                if (p.getPreferencesSize(strClass) > 1) {
190                    JScrollPane scrollPane = new JScrollPane(classholder);
191                    scrollPane.setPreferredSize(new Dimension(300, 300));
192                    scrollPane.setBorder(BorderFactory.createEmptyBorder());
193                    tab.add(scrollPane, p.getClassDescription(strClass));
194                }
195            }
196        }
197        HashMap<String, ArrayList<ListItems>> countOfItems = new HashMap<>();
198        HashMap<String, ArrayList<JCheckBox>> countOfItemsCheck = new HashMap<>();
199        HashMap<String, ArrayList<JComboBox<Object>>> countOfItemsCombo = new HashMap<>();
200
201        for (Entry<JComboBox<Object>, ListItems> entry : _comboBoxes.entrySet()) {
202            if (!entry.getValue().isIncluded()) {
203                String strItem = entry.getValue().getItem();
204                if (!countOfItems.containsKey(strItem)) {
205                    countOfItems.put(strItem, new ArrayList<>());
206                    countOfItemsCombo.put(strItem, new ArrayList<>());
207                }
208                ArrayList<ListItems> a = countOfItems.get(strItem);
209                a.add(entry.getValue());
210
211                ArrayList<JComboBox<Object>> acb = countOfItemsCombo.get(strItem);
212                acb.add(entry.getKey());
213            }
214        }
215
216        for (Entry<JCheckBox, ListItems> entry : _checkBoxes.entrySet()) {
217            if (!entry.getValue().isIncluded()) {
218                String strItem = entry.getValue().getItem();
219                if (!countOfItems.containsKey(strItem)) {
220                    countOfItems.put(strItem, new ArrayList<>());
221                    countOfItemsCheck.put(strItem, new ArrayList<>());
222                }
223                ArrayList<ListItems> a = countOfItems.get(strItem);
224                a.add(entry.getValue());
225
226                ArrayList<JCheckBox> acb = countOfItemsCheck.get(strItem);
227                acb.add(entry.getKey());
228            }
229        }
230
231        JPanel miscPanel = new JPanel();
232        miscPanel.setLayout(new BoxLayout(miscPanel, BoxLayout.Y_AXIS));
233
234        JPanel mischolder = new JPanel();
235        mischolder.setLayout(new BorderLayout());
236        mischolder.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
237        for (Entry<String, ArrayList<ListItems>> entry : countOfItems.entrySet()) {
238            ArrayList<ListItems> a = entry.getValue();
239            ArrayList<JCheckBox> chb = countOfItemsCheck.get(entry.getKey());
240            ArrayList<JComboBox<Object>> cob = countOfItemsCombo.get(entry.getKey());
241            if (a.size() > 1) {
242                JPanel tableDeleteTabPanel = new JPanel();
243                tableDeleteTabPanel.setLayout(new BoxLayout(tableDeleteTabPanel, BoxLayout.Y_AXIS));
244                JLabel tableDeleteInfoLabel = new JLabel(p.getChoiceDescription(a.get(0).getClassName(), a.get(0).getItem()), JLabel.CENTER);
245                tableDeleteInfoLabel.setAlignmentX(0.5f);
246                tableDeleteTabPanel.add(tableDeleteInfoLabel);
247                JPanel inside = new JPanel();
248                if (cob != null) {
249                    JPanel insideCombo = new JPanel();
250                    int gridsize = (int) (Math.ceil((cob.size() / 2.0)));
251                    insideCombo.setLayout(new jmri.util.javaworld.GridLayout2(gridsize, 2 * 2, 10, 2));
252                    for (JComboBox<Object> combo : cob) {
253                        JLabel _comboLabel = new JLabel(p.getClassDescription(_comboBoxes.get(combo).getClassName()), JLabel.RIGHT);
254                        _comboBoxes.get(combo).isIncluded(true);
255                        insideCombo.add(_comboLabel);
256                        insideCombo.add(combo);
257                    }
258                    inside.add(insideCombo);
259                }
260                if (chb != null) {
261                    JPanel insideCheck = new JPanel();
262                    insideCheck.setLayout(new jmri.util.javaworld.GridLayout2(chb.size(), 1));
263                    for (JCheckBox check : chb) {
264                        JLabel _checkLabel = new JLabel(p.getClassDescription(_checkBoxes.get(check).getClassName()), JLabel.RIGHT);
265                        _checkBoxes.get(check).isIncluded(true);
266                        insideCheck.add(_checkLabel);
267                        insideCheck.add(check);
268                    }
269                    inside.add(insideCheck);
270                }
271                tableDeleteTabPanel.add(inside);
272                JScrollPane scrollPane = new JScrollPane(tableDeleteTabPanel);
273                scrollPane.setPreferredSize(new Dimension(300, 300));
274                tab.add(scrollPane, entry.getKey());
275            } else {
276                JPanel itemPanel = new JPanel();
277                JPanel subItem = new JPanel();
278                subItem.setLayout(new BoxLayout(subItem, BoxLayout.Y_AXIS));
279                subItem.add(new JLabel(p.getClassDescription(a.get(0).getClassName()), JLabel.CENTER));
280
281                if (countOfItemsCheck.containsKey(entry.getKey())) {
282                    subItem.add(countOfItemsCheck.get(entry.getKey()).get(0));
283                    itemPanel.add(subItem);
284                    miscPanel.add(itemPanel);
285                }
286            }
287
288            // Include the tabs option
289            JPanel activePanel = new JPanel();
290            JPanel activeItem = new JPanel();
291            activeItem.setLayout(new BoxLayout(activeItem, BoxLayout.Y_AXIS));
292            activeItem.setBorder(BorderFactory.createLineBorder(java.awt.Color.black));
293            activeItem.add(new JLabel(Bundle.getMessage("TabsOption")), JLabel.CENTER);
294            activeItem.add(tabsOption);
295            activePanel.add(activeItem);
296            mischolder.add(activePanel);
297
298        }
299
300        add(tab);
301        mischolder.add(miscPanel, BorderLayout.NORTH);
302        JScrollPane miscScrollPane = new JScrollPane(mischolder);
303        miscScrollPane.setPreferredSize(new Dimension(300, 300));
304
305        tab.add(miscScrollPane, Bundle.getMessage("MiscItems"));
306        revalidate();
307    }
308
309    @Override
310    public String getPreferencesItem() {
311        return "MESSAGES"; // NOI18N
312    }
313
314    @Override
315    public String getPreferencesItemText() {
316        return Bundle.getMessage("MenuMessages"); // NOI18N
317    }
318
319    @Override
320    public String getTabbedPreferencesTitle() {
321        return null;
322    }
323
324    @Override
325    public String getLabelKey() {
326        return null;
327    }
328
329    @Override
330    public JComponent getPreferencesComponent() {
331        return this;
332    }
333
334    @Override
335    public boolean isPersistant() {
336        return false;
337    }
338
339    @Override
340    public String getPreferencesTooltip() {
341        return null;
342    }
343
344    @Override
345    public void savePreferences() {
346        this.updateManager();
347    }
348
349    @Override
350    public synchronized  boolean isDirty() {
351        for (Entry<JComboBox<Object>, ListItems> entry : _comboBoxes.entrySet()) {
352            String strClass = entry.getValue().getClassName();
353            String strItem = entry.getValue().getItem();
354            if (!p.getChoiceOptions(strClass, strItem).get(p.getMultipleChoiceOption(strClass, strItem)).equals(entry.getKey().getSelectedItem())) {
355                return true;
356            }
357        }
358        for (Entry<JCheckBox, ListItems> entry : _checkBoxes.entrySet()) {
359            String strClass = entry.getValue().getClassName();
360            String strItem = entry.getValue().getItem();
361            if (p.getPreferenceState(strClass, strItem) != entry.getKey().isSelected()) {
362                return true;
363            }
364        }
365        return false;
366    }
367
368    @Override
369    public boolean isRestartRequired() {
370        return false;
371    }
372
373    @Override
374    public boolean isPreferencesValid() {
375        return true; // no validity checking performed
376    }
377
378    static class ListItems {
379
380        String strClass;
381        String item;
382        boolean included = false;
383
384        ListItems(String strClass, String item) {
385            this.strClass = strClass;
386            this.item = item;
387        }
388
389        String getClassName() {
390            return strClass;
391        }
392
393        String getItem() {
394            return item;
395        }
396
397        boolean isIncluded() {
398            return included;
399        }
400
401        void isIncluded(boolean boo) {
402            included = boo;
403        }
404    }
405
406    boolean updating = false;
407
408    public synchronized void updateManager() {
409        updating = true;
410        p.setLoading();
411
412        for (Entry<JComboBox<Object>, ListItems> entry : _comboBoxes.entrySet()) {
413            String strClass = entry.getValue().getClassName();
414            String strItem = entry.getValue().getItem();
415            String strSelection = (String) entry.getKey().getSelectedItem();
416            p.setMultipleChoiceOption(strClass, strItem, strSelection);
417        }
418
419        for (Entry<JCheckBox, ListItems> entry : _checkBoxes.entrySet()) {
420            String strClass = entry.getValue().getClassName();
421            String strItem = entry.getValue().getItem();
422            p.setPreferenceState(strClass, strItem, entry.getKey().isSelected());
423        }
424
425        updating = false;
426        p.finishLoading();
427        refreshOptions();
428    }
429
430    protected String getClassName() {
431        return UserMessagePreferencesPane.class.getName();
432    }
433
434    private void refreshOptions() {
435        if (updating) {
436            return;
437        }
438        newMessageTab();
439    }
440
441}