MenuWindows.java

package com.jsql.view.swing.menubar;

import com.formdev.flatlaf.intellijthemes.FlatDarkFlatIJTheme;
import com.formdev.flatlaf.intellijthemes.FlatHighContrastIJTheme;
import com.formdev.flatlaf.intellijthemes.FlatLightFlatIJTheme;
import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatGitHubDarkIJTheme;
import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatGitHubIJTheme;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.jsql.model.InjectionModel;
import com.jsql.util.I18nUtil;
import com.jsql.util.LogLevelUtil;
import com.jsql.util.PreferencesUtil;
import com.jsql.view.swing.action.ActionNewWindow;
import com.jsql.view.swing.dialog.translate.Language;
import com.jsql.view.swing.panel.PanelPreferences;
import com.jsql.view.swing.sql.SqlEngine;
import com.jsql.view.swing.tab.TabHeader;
import com.jsql.view.swing.util.I18nViewUtil;
import com.jsql.view.swing.util.MediatorHelper;
import com.jsql.view.swing.util.UiUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.prefs.Preferences;

public class MenuWindows extends JMenu {

    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();

    private static final String I18N_SQL_ENGINE = "MENUBAR_SQL_ENGINE";
    private static final String I18N_PREFERENCES = "MENUBAR_PREFERENCES";
    private final AppMenubar appMenubar;

    private final JMenu menuView;

    public MenuWindows(AppMenubar appMenubar) {

        super(I18nUtil.valueByKey("MENUBAR_WINDOWS"));
        this.appMenubar = appMenubar;

        this.setName("menuWindows");
        I18nViewUtil.addComponentForKey("MENUBAR_WINDOWS", this);
        this.setMnemonic('W');

        JMenuItem itemNewWindows = new JMenuItem(new ActionNewWindow());
        I18nViewUtil.addComponentForKey("NEW_WINDOW_MENU", itemNewWindows);

        this.add(itemNewWindows);
        var menuAppearance = new JMenu(I18nUtil.valueByKey("MENUBAR_APPEARANCE"));
        I18nViewUtil.addComponentForKey("MENUBAR_APPEARANCE", menuAppearance);
        menuAppearance.setMnemonic('A');

        JMenuItem itemNewWindows4k = new JMenuItem(
            new ActionNewWindow("New 4K Window", "-Dsun.java2d.uiScale=2.5")
        );
        menuAppearance.add(itemNewWindows4k);

        var groupRadio = new ButtonGroup();
        var menuThemes = new JMenu(I18nUtil.valueByKey("MENUBAR_THEMES"));
        I18nViewUtil.addComponentForKey("MENUBAR_THEMES", menuAppearance);
        menuThemes.setMnemonic('T');

        Arrays.asList(
            new AbstractMap.SimpleEntry<>(FlatLightFlatIJTheme.class.getName(), "IntelliJ"),
            new AbstractMap.SimpleEntry<>(FlatDarkFlatIJTheme.class.getName(), "IntelliJ Dark"),
            new AbstractMap.SimpleEntry<>(FlatMacLightLaf.class.getName(), "macOS"),
            new AbstractMap.SimpleEntry<>(FlatMacDarkLaf.class.getName(), "macOS Dark"),
            new AbstractMap.SimpleEntry<>(FlatGitHubIJTheme.class.getName(), "GitHub"),
            new AbstractMap.SimpleEntry<>(FlatGitHubDarkIJTheme.class.getName(), "GitHub Dark"),
            new AbstractMap.SimpleEntry<>(FlatHighContrastIJTheme.class.getName(), "High contrast")
        ).forEach(entry -> {
            JMenuItem item = new JRadioButtonMenuItem(
                new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        AppMenubar.applyTheme(entry.getKey());
                    }
                }
            );
            item.setText(entry.getValue());
            item.setSelected(entry.getKey().equals(MediatorHelper.model().getMediatorUtils().getPreferencesUtil().getThemeFlatLafName()));
            groupRadio.add(item);
            menuThemes.add(item);
        });

        this.add(itemNewWindows);
        this.add(menuAppearance);
        this.add(menuThemes);
        this.add(new JSeparator());
        this.add(this.initMenuTranslation());
        this.add(new JSeparator());

        this.menuView = new JMenu(I18nUtil.valueByKey("MENUBAR_VIEW"));
        I18nViewUtil.addComponentForKey("MENUBAR_VIEW", this.menuView);
        this.menuView.setMnemonic('V');

        AtomicInteger accelerator = new AtomicInteger(0x31);
        AtomicInteger tabPosition = new AtomicInteger();
        MediatorHelper.frame().getTabManagers().getIconsTabs().forEach(entry -> {
            var menuItem = new JMenuItem(I18nUtil.valueByKey(entry.getKeyLabel()), entry.getIcon());
            I18nViewUtil.addComponentForKey(entry.getKeyLabel(), menuItem);
            menuItem.setName(entry.getKeyLabel());  // required by card manager switch
            this.menuView.add(menuItem);

            menuItem.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.getExtendedKeyCodeForChar(accelerator.getAndIncrement()),
                InputEvent.CTRL_DOWN_MASK
            ));

            final var position = tabPosition.get();  // required by closure
            menuItem.addActionListener(actionEvent -> {  // setAction() could set action+text+icon but i18n not easy
                CardLayout cardLayout = (CardLayout) MediatorHelper.tabManagersCards().getLayout();
                cardLayout.show(MediatorHelper.tabManagersCards(), menuItem.getName());
                MediatorHelper.frame().getTabManagers().setSelectedIndex(position);
            });
            tabPosition.getAndIncrement();
        });

        this.add(this.menuView);

        Preferences preferences = Preferences.userRoot().node(InjectionModel.class.getName());

        var menuPanel = new JMenu(I18nUtil.valueByKey("MENUBAR_PANEL"));
        I18nViewUtil.addComponentForKey("MENUBAR_PANEL", menuPanel);
        menuPanel.setMnemonic('C');

        Arrays.asList(
            new ModelCheckboxMenu(
                "CONSOLE_CHUNK_LABEL",
                PreferencesUtil.CHUNK_VISIBLE,
                () -> MediatorHelper.panelConsoles().insertChunkTab(),
                UiUtil.CHUNK.getIcon()
            ),
            new ModelCheckboxMenu(
                "CONSOLE_BINARY_LABEL",
                PreferencesUtil.BINARY_VISIBLE,
                () -> MediatorHelper.panelConsoles().insertBooleanTab(),
                UiUtil.BINARY.getIcon()
            ),
            new ModelCheckboxMenu(
                "CONSOLE_NETWORK_LABEL",
                PreferencesUtil.NETWORK_VISIBLE,
                () -> MediatorHelper.panelConsoles().insertNetworkTab(),
                UiUtil.NETWORK.getIcon()
            ),
            new ModelCheckboxMenu(
                "CONSOLE_JAVA_LABEL",
                PreferencesUtil.JAVA_VISIBLE,
                () -> MediatorHelper.panelConsoles().insertJavaTab(),
                UiUtil.CUP.getIcon()
            )
        ).forEach(model -> {
            var menuItem = new JCheckBoxMenuItem(
                I18nUtil.valueByKey(model.i18n),
                model.icon,
                preferences.getBoolean(model.keyPref, true)
            );
            I18nViewUtil.addComponentForKey(model.i18n, menuItem);
            menuPanel.add(menuItem);

            menuItem.addActionListener(actionEvent -> {
                if (menuItem.isSelected()) {
                    model.runnableInsertTab.run();
                } else {
                    try {  // fix #95874: IndexOutOfBoundsException on remove()
                        MediatorHelper.tabConsoles().remove(MediatorHelper.tabConsoles().indexOfTab(model.icon));
                    } catch (IndexOutOfBoundsException e) {  // should not occur
                        LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e);
                    }
                }
            });
        });

        this.add(menuPanel);
        this.add(new JSeparator());
        this.add(this.getMenuItemSqlEngine());
        this.add(this.getMenuItemPreferences());
    }

    private JMenuItem getMenuItemSqlEngine() {
        var itemSqlEngine = new JMenuItem(I18nUtil.valueByKey(MenuWindows.I18N_SQL_ENGINE));
        I18nViewUtil.addComponentForKey(MenuWindows.I18N_SQL_ENGINE, itemSqlEngine);
        itemSqlEngine.setName("itemSqlEngine");
        itemSqlEngine.setMnemonic('S');

        // Render the SQL Engine dialog behind scene
        var titleTabSqlEngine = "SQL Engine";

        itemSqlEngine.addActionListener(actionEvent -> {
            for (var i = 0; i < MediatorHelper.tabResults().getTabCount() ; i++) {
                if (titleTabSqlEngine.equals(MediatorHelper.tabResults().getTitleAt(i))) {
                    MediatorHelper.tabResults().setSelectedIndex(i);
                    return;
                }
            }

            MediatorHelper.frame().getSplitNS().initSplitOrientation();

            var panelSqlEngine = new SqlEngine();
            MediatorHelper.tabResults().addTab(titleTabSqlEngine, panelSqlEngine);
            MediatorHelper.tabResults().setSelectedComponent(panelSqlEngine);  // Focus on the new tab

            // Create a custom tab header
            var header = new TabHeader(I18nViewUtil.valueByKey(MenuWindows.I18N_SQL_ENGINE), UiUtil.COG.getIcon());
            I18nViewUtil.addComponentForKey(MenuWindows.I18N_SQL_ENGINE, header.getTabLabel());

            // Apply the custom header to the tab
            MediatorHelper.tabResults().setTabComponentAt(MediatorHelper.tabResults().indexOfComponent(panelSqlEngine), header);
            MediatorHelper.tabResults().updateUI();  // required: light, open/close prefs, dark => light artifacts
        });

        return itemSqlEngine;
    }

    private JMenuItem getMenuItemPreferences() {
        JMenuItem itemPreferences = new JMenuItem(I18nUtil.valueByKey(MenuWindows.I18N_PREFERENCES), 'P');
        I18nViewUtil.addComponentForKey(MenuWindows.I18N_PREFERENCES, itemPreferences);
        itemPreferences.setName("itemPreferences");

        // Render the Preferences dialog behind scene
        var titleTabPreferences = "Preferences";

        itemPreferences.addActionListener(actionEvent -> {
            for (var i = 0; i < MediatorHelper.tabResults().getTabCount() ; i++) {
                if (titleTabPreferences.equals(MediatorHelper.tabResults().getTitleAt(i))) {
                    MediatorHelper.tabResults().setSelectedIndex(i);
                    return;
                }
            }

            MediatorHelper.frame().getSplitNS().initSplitOrientation();

            var panelPreferences = new PanelPreferences();
            MediatorHelper.tabResults().addTab(titleTabPreferences, panelPreferences);
            MediatorHelper.tabResults().setSelectedComponent(panelPreferences);  // Focus on the new tab

            // Create a custom tab header
            var header = new TabHeader(I18nViewUtil.valueByKey(MenuWindows.I18N_PREFERENCES), UiUtil.COG.getIcon());
            I18nViewUtil.addComponentForKey(MenuWindows.I18N_PREFERENCES, header.getTabLabel());

            // Apply the custom header to the tab
            MediatorHelper.tabResults().setTabComponentAt(MediatorHelper.tabResults().indexOfComponent(panelPreferences), header);

            MediatorHelper.tabResults().updateUI();  // required: light, open/close prefs, dark => light artifacts
        });

        return itemPreferences;
    }

    private JMenu initMenuTranslation() {
        var menuTranslation = new JMenu(I18nUtil.valueByKey("MENUBAR_LANGUAGE"));
        I18nViewUtil.addComponentForKey("MENUBAR_LANGUAGE", menuTranslation);
        menuTranslation.setName("menuTranslation");
        menuTranslation.setMnemonic('L');

        var groupRadioLanguage = new ButtonGroup();
        var atomicIsAnySelected = new AtomicBoolean(false);
        AppMenubar.MODELS_ITEM.forEach(model -> {
            atomicIsAnySelected.set(atomicIsAnySelected.get() || model.getLanguage().isCurrentLanguage());
            model.setMenuItem(new JRadioButtonMenuItem(
                model.getLanguage().getMenuItemLabel(),
                model.getLanguage().getFlag(),
                model.getLanguage().isCurrentLanguage()
            ));
            model.getMenuItem().addActionListener(actionEvent -> {
                this.appMenubar.switchLocale(
                    model.getLanguage() == Language.EN
                    ? Locale.ROOT  // required as no bundle 'en'
                    : Locale.forLanguageTag(model.getLanguage().getLanguageTag())
                );
                MediatorHelper.model().getMediatorUtils().getPreferencesUtil().withLanguageTag(model.getLanguage().getLanguageTag()).persist();
            });
            menuTranslation.add(model.getMenuItem());
            groupRadioLanguage.add(model.getMenuItem());
        });

        AppMenubar.MODELS_ITEM.stream().filter(model -> model.getLanguage() == Language.EN)
        .forEach(modelItem -> {
            modelItem.getMenuItem().setSelected(!atomicIsAnySelected.get());
            modelItem.getMenuItem().setName("itemEnglish");
        });
        AppMenubar.MODELS_ITEM.stream().filter(model -> model.getLanguage() == Language.RU)
        .forEach(modelItem -> modelItem.getMenuItem().setName("itemRussian"));
        AppMenubar.MODELS_ITEM.stream().filter(model -> model.getLanguage() == Language.AR)
        .forEach(modelItem -> modelItem.getMenuItem().setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT));

        return menuTranslation;
    }

    public void switchLocaleFromPreferences() {
        AppMenubar.MODELS_ITEM.stream()
        .filter(model -> model.getLanguage().getLanguageTag().equals(
            MediatorHelper.model().getMediatorUtils().getPreferencesUtil().getLanguageTag()
        ))
        .forEach(modelItem -> modelItem.getMenuItem().doClick());
    }


    // Getter and setter

    public JMenu getMenuView() {
        return this.menuView;
    }
}