AppMenubar.java

/*******************************************************************************
 * Copyhacked (H) 2012-2025.
 * This program and the accompanying materials
 * are made available under no term at all, use it like
 * you want, but share and discuss it
 * every time possible with every body.
 * 
 * Contributors:
 *      ron190 at ymail dot com - initial implementation
 ******************************************************************************/
package com.jsql.view.swing.menubar;

import com.jsql.util.GitUtil.ShowOnConsole;
import com.jsql.util.I18nUtil;
import com.jsql.util.LogLevelUtil;
import com.jsql.view.swing.action.ActionSaveTab;
import com.jsql.view.swing.action.HotkeyUtil;
import com.jsql.view.swing.console.JTextPaneAppender;
import com.jsql.view.swing.dialog.DialogAbout;
import com.jsql.view.swing.dialog.DialogTranslate;
import com.jsql.view.swing.dialog.translate.Language;
import com.jsql.view.swing.panel.preferences.PanelTampering;
import com.jsql.view.swing.sql.SqlEngine;
import com.jsql.view.swing.table.PanelTable;
import com.jsql.view.swing.text.JPopupTextArea;
import com.jsql.view.swing.util.I18nViewUtil;
import com.jsql.view.swing.util.MediatorHelper;
import com.jsql.view.swing.util.ModelSvgIcon;
import com.jsql.view.swing.util.UiUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.*;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Application main menubar.
 */
public class AppMenubar extends JMenuBar {
    
    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();

    private final MenuWindows menuWindows;

    protected static final List<ModelItemTranslate> MODELS_ITEM = Stream.of(
        Language.EN, Language.FR, Language.RU, Language.ZH, Language.ES, Language.TR, Language.KO, Language.SE, Language.FI,
        Language.AR, Language.CS, Language.IT, Language.PT, Language.PL, Language.IN, Language.NL, Language.RO, Language.DE
    ).map(ModelItemTranslate::new).collect(Collectors.toList());

    private static final List<ModelItemTranslate> MODELS_ITEM_INTO = Stream.of(
        Language.FR, Language.ES, Language.SE, Language.FI, Language.TR, Language.CS, Language.RO, Language.IT, Language.PT, Language.AR,
        Language.PL, Language.RU, Language.ZH, Language.DE, Language.IN, Language.JA, Language.KO, Language.HI, Language.NL, Language.TA
    ).map(ModelItemTranslate::new).collect(Collectors.toList());

    /**
     * Create a menubar on main frame.
     */
    public AppMenubar() {
        this.add(this.initMenuFile());
        this.add(this.initMenuEdit());
        this.add(this.initMenuCommunity());
        this.menuWindows = new MenuWindows(this);
        this.add(this.menuWindows);
        this.add(this.initMenuHelp());
    }

    private JMenu initMenuFile() {
        var menuFile = new JMenu(I18nUtil.valueByKey("MENUBAR_FILE"));
        I18nViewUtil.addComponentForKey("MENUBAR_FILE", menuFile);
        menuFile.setMnemonic('F');

        JMenuItem itemSave = new JMenuItem(new ActionSaveTab());
        I18nViewUtil.addComponentForKey("MENUBAR_FILE_SAVETABAS", itemSave);

        JMenuItem itemExit = new JMenuItem(I18nUtil.valueByKey("MENUBAR_FILE_EXIT"), 'x');
        I18nViewUtil.addComponentForKey("MENUBAR_FILE_EXIT", itemExit);
        itemExit.addActionListener(actionEvent -> MediatorHelper.frame().dispose());

        HotkeyUtil.addShortcut(this);

        menuFile.add(itemSave);
        menuFile.add(new JSeparator());
        menuFile.add(itemExit);
        return menuFile;
    }

    private JMenu initMenuEdit() {
        var menuEdit = new JMenu(I18nUtil.valueByKey("MENUBAR_EDIT"));
        I18nViewUtil.addComponentForKey("MENUBAR_EDIT", menuEdit);
        menuEdit.setMnemonic('E');

        JMenuItem itemCopy = new JMenuItem(I18nUtil.valueByKey("CONTEXT_MENU_COPY"), 'C');
        I18nViewUtil.addComponentForKey("CONTEXT_MENU_COPY", itemCopy);
        itemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
        itemCopy.addActionListener(actionEvent -> {
            if (MediatorHelper.tabResults().getSelectedComponent() instanceof PanelTable) {
                ((PanelTable) MediatorHelper.tabResults().getSelectedComponent()).copyTable();
            } else if (MediatorHelper.tabResults().getSelectedComponent() instanceof JScrollPane) {
                ((JTextComponent) ((JScrollPane) MediatorHelper.tabResults().getSelectedComponent()).getViewport().getView()).copy();
            }
        });

        JMenuItem itemSelectAll = new JMenuItem(I18nUtil.valueByKey("CONTEXT_MENU_SELECT_ALL"), 'A');
        I18nViewUtil.addComponentForKey("CONTEXT_MENU_SELECT_ALL", itemSelectAll);
        itemSelectAll.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK));
        itemSelectAll.addActionListener(actionEvent -> {
            if (MediatorHelper.tabResults().getSelectedComponent() instanceof PanelTable) {
                ((PanelTable) MediatorHelper.tabResults().getSelectedComponent()).selectTable();
            } else if (MediatorHelper.tabResults().getSelectedComponent() instanceof JScrollPane) {
                // Textarea need focus to select all
                ((JScrollPane) MediatorHelper.tabResults().getSelectedComponent()).getViewport().getView().requestFocusInWindow();
                ((JTextComponent) ((JScrollPane) MediatorHelper.tabResults().getSelectedComponent()).getViewport().getView()).selectAll();
            }
        });

        menuEdit.add(itemCopy);
        menuEdit.add(new JSeparator());
        menuEdit.add(itemSelectAll);
        return menuEdit;
    }

    private JMenu initMenuCommunity() {
        var menuCommunity = new JMenu(I18nUtil.valueByKey("MENUBAR_COMMUNITY"));
        menuCommunity.setMnemonic('C');
        menuCommunity.setName("menuCommunity");
        I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY", menuCommunity);

        JMenu menuI18nContribution = this.initMenuI18nContribution();
        menuI18nContribution.setName("menuI18nContribution");

        JMenuItem itemReportIssue = this.initItemReportIssue();
        itemReportIssue.setName("itemReportIssue");

        menuCommunity.add(menuI18nContribution);
        menuCommunity.add(new JSeparator());
        menuCommunity.add(itemReportIssue);
        return menuCommunity;
    }

    private JMenu initMenuI18nContribution() {
        var menuI18nContribution = new JMenu(I18nUtil.valueByKey("MENUBAR_COMMUNITY_HELPTRANSLATE"));
        I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY_HELPTRANSLATE", menuI18nContribution);

        final var dialogTranslate = new DialogTranslate();  // Render the About dialog behind scene
        AppMenubar.MODELS_ITEM_INTO.forEach(model -> {
            model.setMenuItem(new JMenuItem(model.getLanguage().getMenuItemLabel(), model.getLanguage().getFlag()));
            model.getMenuItem().addActionListener(new ActionTranslate(dialogTranslate, model.getLanguage()));
            menuI18nContribution.add(model.getMenuItem());
        });

        var itemIntoOther = new JMenuItem(I18nUtil.valueByKey("MENUBAR_COMMUNITY_ANOTHERLANGUAGE"));
        I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY_ANOTHERLANGUAGE", itemIntoOther);

        AppMenubar.MODELS_ITEM_INTO.stream().filter(model -> model.getLanguage() == Language.AR)
        .forEach(modelItemTranslate -> modelItemTranslate.getMenuItem().setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT));
        AppMenubar.MODELS_ITEM_INTO.stream().filter(model -> model.getLanguage() == Language.FR)
        .forEach(modelItemTranslate -> modelItemTranslate.getMenuItem().setName("itemIntoFrench"));

        menuI18nContribution.add(new JSeparator());
        menuI18nContribution.add(itemIntoOther);
        itemIntoOther.addActionListener(new ActionTranslate(dialogTranslate, Language.OT));

        return menuI18nContribution;
    }

    private JMenuItem initItemReportIssue() {
        JMenuItem itemReportIssue = new JMenuItem(I18nUtil.valueByKey("MENUBAR_COMMUNITY_REPORTISSUE"), 'R');
        I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY_REPORTISSUE", itemReportIssue);

        itemReportIssue.addActionListener(actionEvent -> {

            var panel = new JPanel(new BorderLayout());
            final JTextArea textarea = new JPopupTextArea(new JTextArea()).getProxy();
            textarea.setFont(new Font(
                UiUtil.FONT_NAME_MONOSPACED,
                Font.PLAIN,
                UIManager.getDefaults().getFont("TextField.font").getSize()
            ));
            textarea.setText(
                "## What's the expected behavior?\n\n"
                + "## What's the actual behavior?\n\n"
                + "## Any other detailed information on the Issue?\n\n"
                + "## Steps to reproduce the problem\n\n"
                + "  1. ...\n"
                + "  2. ...\n\n"
                + "## [Community] Request for new feature\n\n"
            );
            panel.add(new JLabel("Describe your bug or issue:"), BorderLayout.NORTH);
            panel.add(new JScrollPane(textarea));
            panel.setPreferredSize(new Dimension(500, 350));
            panel.setMinimumSize(new Dimension(500, 350));

            textarea.addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    super.mousePressed(e);
                    textarea.requestFocusInWindow();
                }
            });

            int result = JOptionPane.showOptionDialog(
                MediatorHelper.frame(),
                panel,
                "Report an issue or a bug",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                new String[] {
                    "Send",
                    I18nUtil.valueByKey("LIST_ADD_VALUE_CANCEL")
                },
                I18nUtil.valueByKey("LIST_ADD_VALUE_CANCEL")
            );

            if (StringUtils.isNotEmpty(textarea.getText()) && result == JOptionPane.YES_OPTION) {
                MediatorHelper.model().getMediatorUtils().getGitUtil().sendReport(textarea.getText(), ShowOnConsole.YES, "Report");
            }
        });

        return itemReportIssue;
    }

    private JMenu initMenuHelp() {
        var menuHelp = new JMenu(I18nUtil.valueByKey("MENUBAR_HELP"));
        menuHelp.setMnemonic('H');
        I18nViewUtil.addComponentForKey("MENUBAR_HELP", menuHelp);
        menuHelp.setName("menuHelp");

        JMenuItem itemHelp = new JMenuItem(I18nUtil.valueByKey("MENUBAR_HELP_ABOUT"), 'A');
        I18nViewUtil.addComponentForKey("MENUBAR_HELP_ABOUT", itemHelp);
        itemHelp.setName("itemHelp");

        JMenuItem itemUpdate = new JMenuItem(I18nUtil.valueByKey("MENUBAR_HELP_UPDATE"), 'U');
        I18nViewUtil.addComponentForKey("MENUBAR_HELP_UPDATE", itemUpdate);

        // Render the About dialog behind scene
        itemHelp.addActionListener(actionEvent -> {
            final var dialogAbout = new DialogAbout();
            if (!dialogAbout.isVisible()) {
                dialogAbout.initDialog();
                dialogAbout.setVisible(true);  // needed here for button focus
                dialogAbout.requestButtonFocus();
            }
            dialogAbout.setVisible(true);
        });
        itemUpdate.addActionListener(new ActionCheckUpdate());

        menuHelp.add(itemUpdate);
        menuHelp.add(new JSeparator());
        menuHelp.add(itemHelp);
        return menuHelp;
    }

    public static void applyTheme(String theme) {
        UiUtil.applyTheme(theme);

        Arrays.asList(
            UiUtil.DATABASE_BOLD, UiUtil.ADMIN, UiUtil.DOWNLOAD, UiUtil.TERMINAL, UiUtil.UPLOAD, UiUtil.LOCK, UiUtil.TEXTFIELD, UiUtil.BATCH,
            UiUtil.TABLE_LINEAR, UiUtil.TABLE_BOLD, UiUtil.NETWORK, UiUtil.DATABASE_LINEAR, UiUtil.COG, UiUtil.CUP, UiUtil.CONSOLE, UiUtil.BINARY, UiUtil.CHUNK,
            UiUtil.ARROW, UiUtil.ARROW_HOVER, UiUtil.ARROW_PRESSED, UiUtil.EXPAND, UiUtil.EXPAND_HOVER, UiUtil.EXPAND_PRESSED,
            UiUtil.HOURGLASS, UiUtil.ARROW_DOWN, UiUtil.ARROW_UP, UiUtil.SQUARE, UiUtil.GLOBE, UiUtil.TICK_GREEN, UiUtil.CROSS_RED,
            UiUtil.APP_ICON, UiUtil.APP_BIG, UiUtil.APP_MIDDLE
        ).forEach(ModelSvgIcon::setColorFilter);

        SqlEngine.applyTheme();
        PanelTampering.applyTheme();
        MediatorHelper.panelConsoles().getTabbedPaneNetworkTab().applyTheme();
        MediatorHelper.frame().setIconImages(UiUtil.getIcons());
        MediatorHelper.frame().revalidate();

        MediatorHelper.model().getMediatorUtils().getPreferencesUtil().withThemeFlatLafName(theme).persist();
    }
    
    public void switchLocale(Locale newLocale) {
        this.switchLocale(I18nUtil.getCurrentLocale(), newLocale, false);
    }
    
    public void switchLocaleWithStatus(Locale oldLocale, Locale newLocale, boolean isStartup) {
        this.switchLocale(oldLocale, newLocale, isStartup);
        MediatorHelper.model().getPropertiesUtil().displayStatus(newLocale);
    }

    public void switchLocale(Locale oldLocale, Locale newLocale, boolean isStartup) {
        I18nUtil.setCurrentBundle(newLocale);

        Stream.of(
            JTextPaneAppender.ATTRIBUTE_WARN,
            JTextPaneAppender.ATTRIBUTE_INFORM,
            JTextPaneAppender.ATTRIBUTE_SUCCESS,
            JTextPaneAppender.ATTRIBUTE_ALL
        )
        .forEach(attribute -> {
            StyleConstants.setFontFamily(attribute, I18nViewUtil.isNonUbuntu(newLocale) ? UiUtil.FONT_NAME_MONO_ASIAN : UiUtil.FONT_NAME_MONO_NON_ASIAN);
            StyleConstants.setFontSize(attribute, I18nViewUtil.isNonUbuntu(newLocale) ? UiUtil.FONT_SIZE_MONO_ASIAN : UiUtil.FONT_SIZE_MONO_NON_ASIAN);
        });

        MediatorHelper.managerBruteForce().getResult().setFont(I18nViewUtil.isNonUbuntu(newLocale) ? UiUtil.FONT_MONO_ASIAN : UiUtil.FONT_MONO_NON_ASIAN);

        this.switchNetworkTable();
        I18nViewUtil.switchI18nComponents();
        this.switchOrientation(oldLocale, newLocale, isStartup);
        this.switchMenuItems();  // required to restore proper language orientation
        
        MediatorHelper.treeDatabase().reloadNodes();

        // IllegalArgumentException #92981 on revalidate()
        try {
            MediatorHelper.frame().revalidate();  // Fix glitches on Linux
        } catch (IllegalArgumentException e) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        }
    }

    private void switchOrientation(Locale oldLocale, Locale newLocale, boolean isStartup) {
        var componentOrientation = ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale());
        MediatorHelper.frame().applyComponentOrientation(componentOrientation);
        
        if (!ComponentOrientation.getOrientation(oldLocale).equals(ComponentOrientation.getOrientation(newLocale))) {
            
            JSplitPane splitPaneLeftRight = MediatorHelper.frame().getSplitNS().getSplitEW();
            var componentLeft = splitPaneLeftRight.getLeftComponent();
            var componentRight = splitPaneLeftRight.getRightComponent();

            // Reset components
            splitPaneLeftRight.setLeftComponent(null);
            splitPaneLeftRight.setRightComponent(null);
            splitPaneLeftRight.setLeftComponent(componentRight);
            splitPaneLeftRight.setRightComponent(componentLeft);

            if (isStartup) {
                // TODO unclear and not working properly when starting as locale arabic
                splitPaneLeftRight.setDividerLocation(
                    splitPaneLeftRight.getDividerLocation()
                );
            } else {  // required as switch to arabic uses reversed location
                splitPaneLeftRight.setDividerLocation(
                    splitPaneLeftRight.getWidth() -
                    splitPaneLeftRight.getDividerLocation()
                );
            }
        }
        
        MediatorHelper.tabResults().setComponentOrientation(ComponentOrientation.getOrientation(newLocale));
    }

    private void switchMenuItems() {
        Stream.concat(AppMenubar.MODELS_ITEM.stream(), AppMenubar.MODELS_ITEM_INTO.stream())
        .forEach(model -> model.getMenuItem().setComponentOrientation(
            model.getLanguage().isRightToLeft()
            ? ComponentOrientation.RIGHT_TO_LEFT
            : ComponentOrientation.LEFT_TO_RIGHT
        ));
    }

    private void switchNetworkTable() {
        JTableHeader header = MediatorHelper.panelConsoles().getNetworkTable().getTableHeader();
        TableColumnModel columnModel = header.getColumnModel();
        columnModel.getColumn(0).setHeaderValue(I18nUtil.valueByKey("NETWORK_TAB_URL_COLUMN"));
        columnModel.getColumn(1).setHeaderValue(I18nUtil.valueByKey("NETWORK_TAB_SIZE_COLUMN") +" (KB)");
        columnModel.getColumn(2).setHeaderValue(I18nUtil.valueByKey("SQLENGINE_STRATEGY"));
        columnModel.getColumn(3).setHeaderValue(I18nUtil.valueByKey("SQLENGINE_METADATA"));
        header.repaint();
    }
    
    
    // Getter and setter

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

    public MenuWindows getMenuWindows() {
        return this.menuWindows;
    }
}