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