001package apps;
002
003import static jmri.util.gui.GuiLafPreferencesManager.MIN_FONT_SIZE;
004
005import java.awt.FlowLayout;
006import java.awt.event.ActionEvent;
007import java.awt.event.ItemEvent;
008import java.text.MessageFormat;
009import java.util.Arrays;
010import java.util.HashMap;
011import java.util.Locale;
012import javax.swing.BoxLayout;
013import javax.swing.ButtonGroup;
014import javax.swing.DefaultComboBoxModel;
015import javax.swing.JButton;
016import javax.swing.JCheckBox;
017import javax.swing.JComboBox;
018import javax.swing.JComponent;
019import javax.swing.JLabel;
020import javax.swing.JPanel;
021import javax.swing.JRadioButton;
022import javax.swing.JSpinner;
023import javax.swing.SpinnerNumberModel;
024import javax.swing.SwingUtilities;
025import javax.swing.UIManager;
026import javax.swing.event.ChangeEvent;
027import jmri.InstanceManager;
028import jmri.profile.Profile;
029import jmri.profile.ProfileManager;
030import jmri.swing.PreferencesPanel;
031import jmri.util.gui.GuiLafPreferencesManager;
032import jmri.util.swing.JComboBoxUtil;
033import org.openide.util.lookup.ServiceProvider;
034
035/**
036 * Provide GUI to configure Swing GUI LAF defaults
037 * <p>
038 * Provides GUI configuration for SWING LAF by displaying radio buttons for each
039 * LAF implementation available. This information is then persisted separately
040 * by the {@link jmri.util.gui.GuiLafPreferencesManager}.
041 * <p>
042 * Locale default language and country is also considered a GUI (and perhaps
043 * LAF) configuration item.
044 *
045 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2010
046 * @since 2.9.5 (Previously in jmri package)
047 */
048@ServiceProvider(service = PreferencesPanel.class)
049public final class GuiLafConfigPane extends JPanel implements PreferencesPanel {
050
051    public static final int MAX_TOOLTIP_TIME = 3600;
052    public static final int MIN_TOOLTIP_TIME = 1;
053
054    /**
055     * Smallest font size shown to a user ({@value}).
056     *
057     * @see GuiLafPreferencesManager#MIN_FONT_SIZE
058     */
059    public static final int MIN_DISPLAYED_FONT_SIZE = MIN_FONT_SIZE;
060    /**
061     * Largest font size shown to a user ({@value}).
062     *
063     * @see GuiLafPreferencesManager#MAX_FONT_SIZE
064     */
065    public static final int MAX_DISPLAYED_FONT_SIZE = 20;
066
067    private final JComboBox<String> localeBox = new JComboBox<>(new String[]{
068        Locale.getDefault().getDisplayName(),
069        "(Please Wait)"});
070    private final HashMap<String, Locale> locale = new HashMap<>();
071    private final ButtonGroup LAFGroup = new ButtonGroup();
072    public JCheckBox mouseEvent;
073    private JComboBox<Integer> fontSizeComboBox;
074    public JCheckBox graphicStateDisplay;
075    public JCheckBox tabbedOblockEditor;
076    public JCheckBox editorUseOldLocSizeDisplay;
077    public ButtonGroup fileChooserGroup= new ButtonGroup();
078    public JCheckBox force100percentScaling;
079
080    public GuiLafConfigPane() {
081        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
082        JPanel p;
083        doLAF(p = new JPanel());
084        add(p);
085        doFontSize(p = new JPanel());
086        add(p);
087        doFileChooserLayoutType(p = new JPanel());
088        add(p);
089        doClickSelection(p = new JPanel());
090        add(p);
091        doGraphicState(p = new JPanel());
092        add(p);
093        doTabbedOblockEditor(p = new JPanel());
094        add(p);
095        doEditorUseOldLocSize(p = new JPanel());
096        add(p);
097        doForce100percentScaling(p = new JPanel());
098        add(p);
099        doMaxComboRows(p = new JPanel());
100        add(p);
101        doToolTipDismissDelay(p = new JPanel());
102        add(p);
103    }
104
105    void doClickSelection(JPanel panel) {
106        panel.setLayout(new FlowLayout());
107        mouseEvent = new JCheckBox(ConfigBundle.getMessage("GUIButtonNonStandardRelease"));
108        mouseEvent.addItemListener((ItemEvent e) -> {
109            InstanceManager.getDefault(GuiLafPreferencesManager.class).setNonStandardMouseEvent(mouseEvent.isSelected());
110        });
111        panel.add(mouseEvent);
112    }
113
114    void doGraphicState(JPanel panel) {
115        panel.setLayout(new FlowLayout());
116        graphicStateDisplay = new JCheckBox(ConfigBundle.getMessage("GUIGraphicTableState"));
117        graphicStateDisplay.setSelected(InstanceManager.getDefault(GuiLafPreferencesManager.class).isGraphicTableState());
118        graphicStateDisplay.addItemListener((ItemEvent e) -> {
119            InstanceManager.getDefault(GuiLafPreferencesManager.class).setGraphicTableState(graphicStateDisplay.isSelected());
120        });
121        panel.add(graphicStateDisplay);
122    }
123
124    void doTabbedOblockEditor(JPanel panel) {
125        panel.setLayout(new FlowLayout());
126        tabbedOblockEditor = new JCheckBox(ConfigBundle.getMessage("GUITabbedOblockEditor"));
127        tabbedOblockEditor.setSelected(InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed());
128        tabbedOblockEditor.setToolTipText(ConfigBundle.getMessage("GUIToolTipTabbedEdit"));
129        tabbedOblockEditor.addItemListener((ItemEvent e) -> {
130            InstanceManager.getDefault(GuiLafPreferencesManager.class).setOblockEditTabbed(tabbedOblockEditor.isSelected());
131        });
132        panel.add(tabbedOblockEditor);
133    }
134
135    void doEditorUseOldLocSize(JPanel panel) {
136        panel.setLayout(new FlowLayout());
137        editorUseOldLocSizeDisplay = new JCheckBox(ConfigBundle.getMessage("GUIUseOldLocSize"));
138        editorUseOldLocSizeDisplay.setSelected(InstanceManager.getDefault(GuiLafPreferencesManager.class).isEditorUseOldLocSize());
139        editorUseOldLocSizeDisplay.addItemListener((ItemEvent e) -> {
140            InstanceManager.getDefault(GuiLafPreferencesManager.class).setEditorUseOldLocSize(editorUseOldLocSizeDisplay.isSelected());
141        });
142        panel.add(editorUseOldLocSizeDisplay);
143    }
144
145    void doFileChooserLayoutType(JPanel panel) {
146        panel.setLayout(new FlowLayout());
147        JLabel fileChooserLabel = new JLabel(ConfigBundle.getMessage("GUIJChooserUseOption"));
148        panel.add(fileChooserLabel);
149                // make the radio buttons
150        JRadioButton jButDefault = new JRadioButton(ConfigBundle.getMessage("GUIJChooserUseDefault"));
151        panel.add(jButDefault);
152        fileChooserGroup.add(jButDefault);
153        jButDefault.addActionListener((ActionEvent e) -> {
154            if (((JRadioButton)e.getSource()).isSelected() ) {
155                InstanceManager.getDefault(GuiLafPreferencesManager.class).setJFileChooserFormat(0);
156            }
157        });
158        JRadioButton jButList = new JRadioButton(ConfigBundle.getMessage("GUIJChooserUseList"));
159        panel.add(jButList);
160        fileChooserGroup.add(jButList);
161        jButList.addActionListener((ActionEvent e) -> {
162            if (((JRadioButton)e.getSource()).isSelected() ) {
163                InstanceManager.getDefault(GuiLafPreferencesManager.class).setJFileChooserFormat(1);
164            }
165        });
166        JRadioButton jButDetail = new JRadioButton(ConfigBundle.getMessage("GUIJChooserUseDetail"));
167        panel.add(jButDetail);
168        fileChooserGroup.add(jButDetail);
169        jButDetail.addActionListener((ActionEvent e) -> {
170            if (((JRadioButton)e.getSource()).isSelected() ) {
171                InstanceManager.getDefault(GuiLafPreferencesManager.class).setJFileChooserFormat(2);
172            }
173        });
174        switch (InstanceManager.getDefault(GuiLafPreferencesManager.class).getJFileChooserFormat()) {
175            case 0:
176                jButDefault.setSelected(true);
177                break;
178            case 1:
179                jButList.setSelected(true);
180                break;
181            case 2:
182                jButDetail.setSelected(true);
183                break;
184            default:
185                jButDefault.setSelected(true);
186        }
187    }
188
189    void doForce100percentScaling(JPanel panel) {
190        jmri.util.EarlyInitializationPreferences eip =
191                jmri.util.EarlyInitializationPreferences.getInstance();
192
193        panel.setLayout(new FlowLayout());
194        force100percentScaling = new JCheckBox(ConfigBundle.getMessage("GUIForce100percentScaling"));
195        force100percentScaling.setSelected(eip.getGUIForce100percentScaling());
196        force100percentScaling.addItemListener((ItemEvent e) -> {
197            eip.setGUIForce100percentScaling(force100percentScaling.isSelected());
198        });
199        panel.add(force100percentScaling);
200    }
201
202    void doLAF(JPanel panel) {
203        // find L&F definitions from Swing
204        panel.setLayout(new FlowLayout());
205        UIManager.LookAndFeelInfo[] plafs = UIManager.getInstalledLookAndFeels();
206        HashMap<String, String> installedLAFs = new HashMap<>(plafs.length);
207        for (UIManager.LookAndFeelInfo plaf : plafs) {
208            installedLAFs.put(plaf.getName(), plaf.getClassName());
209        }
210        
211        // explicitly add the desired DarkLaf LaFs
212        installedLAFs.put("DarkLAF HC", "com.github.weisj.darklaf.theme.HighContrastDarkTheme");
213        // The LaFs available are
214        //  com/github/weisj/darklaf/theme/DarculaTheme
215        //  com/github/weisj/darklaf/theme/HighContrastDarkTheme
216        //  com/github/weisj/darklaf/theme/HighContrastLightTheme
217        //  com/github/weisj/darklaf/theme/IntelliJTheme
218        //  com/github/weisj/darklaf/theme/OneDarkTheme
219        //  com/github/weisj/darklaf/theme/SolarizedDarkTheme
220        //  com/github/weisj/darklaf/theme/SolarizedLightTheme
221        // But we're only listing a subset to avoid overwhelming the user
222        // See GuiLafPreferencesManager.applyLookAndFeel(..) for matching code
223
224        // make the radio buttons
225        for (java.util.Map.Entry<String, String> entry : installedLAFs.entrySet()) {
226            String name = entry.getKey();
227            JRadioButton jmi = new JRadioButton(name);
228            panel.add(jmi);
229            LAFGroup.add(jmi);
230            jmi.setActionCommand(name);
231            jmi.addActionListener((ActionEvent e) -> {
232                InstanceManager.getDefault(GuiLafPreferencesManager.class).setLookAndFeel(installedLAFs.get(name));
233            });
234            if ( entry.getValue().equals(UIManager.getLookAndFeel().getClass().getName())) {
235                jmi.setSelected(true);
236            }
237        }
238    }
239
240    /**
241     * Create and return a JPanel for configuring default local.
242     * <p>
243     * Most of the action is handled in a separate thread, which replaces the
244     * contents of a JComboBox when the list of Locales is available.
245     *
246     * @return the panel
247     */
248    public JPanel doLocale() {
249        JPanel panel = new JPanel();
250        // add JComboBoxen for language and country
251        panel.setLayout(new FlowLayout());
252        panel.add(localeBox);
253        JComboBoxUtil.setupComboBoxMaxRows(localeBox);
254
255        // create object to find locales in new Thread
256        Runnable r = () -> {
257            Locale[] locales = Locale.getAvailableLocales();
258            String[] localeNames = new String[locales.length];
259            for (int i = 0; i < locales.length; i++) {
260                locale.put(locales[i].getDisplayName(), locales[i]);
261                localeNames[i] = locales[i].getDisplayName();
262            }
263            Arrays.sort(localeNames);
264            Runnable update = () -> {
265                localeBox.setModel(new DefaultComboBoxModel<>(localeNames));
266                //localeBox.setModel(new javax.swing.DefaultComboBoxModel(locale.keySet().toArray()));
267                localeBox.setSelectedItem(Locale.getDefault().getDisplayName());
268                localeBox.addActionListener((ActionEvent e) -> {
269                    InstanceManager.getDefault(GuiLafPreferencesManager.class).setLocale(locale.getOrDefault(localeBox.getSelectedItem(), Locale.getDefault()));
270                });
271            };
272            SwingUtilities.invokeLater(update);
273        };
274        new Thread(r).start();
275        return panel;
276    }
277
278    public void setLocale(String loc) {
279        localeBox.setSelectedItem(loc);
280    }
281
282    /**
283     * Get the currently configured Locale or Locale.getDefault if no
284     * configuration has been done.
285     *
286     * @return the in-use Locale
287     */
288    @Override
289    public Locale getLocale() {
290        Locale desired = locale.get(localeBox.getSelectedItem().toString());
291        return (desired != null) ? desired : Locale.getDefault();
292    }
293
294    public void doFontSize(JPanel panel) {
295        GuiLafPreferencesManager manager = InstanceManager.getDefault(GuiLafPreferencesManager.class);
296        Integer[] sizes = new Integer[MAX_DISPLAYED_FONT_SIZE - MIN_DISPLAYED_FONT_SIZE + 1];
297        for (int i = 0; i < sizes.length; i++) {
298            sizes[i] = i + MIN_DISPLAYED_FONT_SIZE;
299        }
300        fontSizeComboBox = new JComboBox<>(sizes);
301        fontSizeComboBox.setEditable(true); // allow users to set font sizes not listed
302        JLabel fontSizeLabel = new JLabel(ConfigBundle.getMessage("ConsoleFontSize"));
303        fontSizeComboBox.setSelectedItem(manager.getFontSize());
304        JLabel fontSizeUoM = new JLabel(ConfigBundle.getMessage("ConsoleFontSizeUoM"));
305        JButton resetButton = new JButton(ConfigBundle.getMessage("ResetDefault"));
306        resetButton.setToolTipText(ConfigBundle.getMessage("GUIFontSizeReset"));
307
308        panel.add(fontSizeLabel);
309        panel.add(fontSizeComboBox);
310        panel.add(fontSizeUoM);
311        panel.add(resetButton);
312
313        JComboBoxUtil.setupComboBoxMaxRows(fontSizeComboBox);
314
315        fontSizeComboBox.addActionListener((ActionEvent e) -> {
316            manager.setFontSize((int) fontSizeComboBox.getSelectedItem());
317        });
318        resetButton.addActionListener((ActionEvent e) -> {
319            if ((int) fontSizeComboBox.getSelectedItem() != manager.getDefaultFontSize()) {
320                fontSizeComboBox.setSelectedItem(manager.getDefaultFontSize());
321            }
322        });
323    }
324
325    private JSpinner maxComboRowsSpinner;
326
327    public void doMaxComboRows(JPanel panel) {
328        GuiLafPreferencesManager manager = InstanceManager.getDefault(GuiLafPreferencesManager.class);
329        JLabel maxComboRowsLabel = new JLabel(ConfigBundle.getMessage("GUIMaxComboRows"));
330        maxComboRowsSpinner = new JSpinner(new SpinnerNumberModel(manager.getMaxComboRows(), 0, 999, 1));
331        this.maxComboRowsSpinner.addChangeListener((ChangeEvent e) -> {
332            manager.setMaxComboRows((int) maxComboRowsSpinner.getValue());
333        });
334        this.maxComboRowsSpinner.setToolTipText(ConfigBundle.getMessage("GUIMaxComboRowsToolTip"));
335        maxComboRowsLabel.setToolTipText(this.maxComboRowsSpinner.getToolTipText());
336        panel.add(maxComboRowsLabel);
337        panel.add(maxComboRowsSpinner);
338    }
339
340    private JSpinner toolTipDismissDelaySpinner;
341
342    public void doToolTipDismissDelay(JPanel panel) {
343        GuiLafPreferencesManager manager = InstanceManager.getDefault(GuiLafPreferencesManager.class);
344        JLabel toolTipDismissDelayLabel = new JLabel(ConfigBundle.getMessage("GUIToolTipDismissDelay"));
345        toolTipDismissDelaySpinner = new JSpinner(new SpinnerNumberModel(manager.getToolTipDismissDelay() / 1000, MIN_TOOLTIP_TIME, MAX_TOOLTIP_TIME, 1));
346        this.toolTipDismissDelaySpinner.addChangeListener((ChangeEvent e) -> {
347            manager.setToolTipDismissDelay((int) toolTipDismissDelaySpinner.getValue() * 1000); // convert to milliseconds from seconds
348        });
349        this.toolTipDismissDelaySpinner.setToolTipText(MessageFormat.format(ConfigBundle.getMessage("GUIToolTipDismissDelayToolTip"), MIN_TOOLTIP_TIME, MAX_TOOLTIP_TIME));
350        toolTipDismissDelayLabel.setToolTipText(this.toolTipDismissDelaySpinner.getToolTipText());
351        JLabel toolTipDismissDelayUoM = new JLabel(ConfigBundle.getMessage("GUIToolTipDismissDelayUoM"));
352        toolTipDismissDelayUoM.setToolTipText(this.toolTipDismissDelaySpinner.getToolTipText());
353        panel.add(toolTipDismissDelayLabel);
354        panel.add(toolTipDismissDelaySpinner);
355        panel.add(toolTipDismissDelayUoM);
356    }
357
358    public String getClassName() {
359        return LAFGroup.getSelection().getActionCommand();
360
361    }
362
363    @Override
364    public String getPreferencesItem() {
365        return "DISPLAY"; // NOI18N
366    }
367
368    @Override
369    public String getPreferencesItemText() {
370        return ConfigBundle.getMessage("MenuDisplay"); // NOI18N
371    }
372
373    @Override
374    public String getTabbedPreferencesTitle() {
375        return ConfigBundle.getMessage("TabbedLayoutGUI"); // NOI18N
376    }
377
378    @Override
379    public String getLabelKey() {
380        return ConfigBundle.getMessage("LabelTabbedLayoutGUI"); // NOI18N
381    }
382
383    @Override
384    public JComponent getPreferencesComponent() {
385        return this;
386    }
387
388    @Override
389    public boolean isPersistant() {
390        return true;
391    }
392
393    @Override
394    public String getPreferencesTooltip() {
395        return null;
396    }
397
398    @Override
399    public void savePreferences() {
400        Profile profile = ProfileManager.getDefault().getActiveProfile();
401        if (profile != null) {
402            InstanceManager.getDefault(GuiLafPreferencesManager.class).savePreferences(profile);
403        }
404    }
405
406    @Override
407    public boolean isDirty() {
408        return InstanceManager.getDefault(GuiLafPreferencesManager.class).isDirty();
409    }
410
411    @Override
412    public boolean isRestartRequired() {
413        return InstanceManager.getDefault(GuiLafPreferencesManager.class).isRestartRequired();
414    }
415
416    @Override
417    public boolean isPreferencesValid() {
418        return true; // no validity checking performed
419    }
420}