JFrameView.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;

import com.jsql.model.InjectionModel;
import com.jsql.util.*;
import com.jsql.view.interaction.SubscriberInteraction;
import com.jsql.view.swing.action.HotkeyUtil;
import com.jsql.view.swing.menubar.AppMenubar;
import com.jsql.view.swing.panel.PanelAddressBar;
import com.jsql.view.swing.panel.split.SplitNS;
import com.jsql.view.swing.terminal.AbstractExploit;
import com.jsql.view.swing.tab.TabManagers;
import com.jsql.view.swing.util.MediatorHelper;
import com.jsql.view.swing.util.UiUtil;
import org.apache.commons.lang3.SystemUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.prefs.Preferences;
import java.util.stream.Stream;

/**
 * View in the MVC pattern, defines all the components
 * and process actions sent by the model.<br>
 * Main groups of components:<br>
 * - at the top: textfield inputs,<br>
 * - at the center: tree on the left, table on the right,<br>
 * - at the bottom: information labels.
 */
public class JFrameView extends JFrame {

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

    /**
     * Map of terminal by unique identifier.
     */
    private final Map<UUID, AbstractExploit> mapUuidShell = new HashMap<>();
    private final transient SubscriberInteraction subscriber = new SubscriberInteraction("com.jsql.view.swing.interaction");
    private TabManagers tabManagers;
    private boolean isMaximized = false;
    private final InjectionModel injectionModel;
    private SplitNS splitNS;  // main

    public JFrameView(InjectionModel injectionModel) {  // Build the GUI: add app icon, tree icons, the 3 main panels
        super(StringUtil.APP_NAME);
        this.injectionModel = injectionModel;
        MediatorHelper.register(this);
        UiUtil.prepareGUI();  // Load UI before any component
        this.initPaneComponents();
        this.initWindow();
        this.initShortcuts();
        this.displayVersion();
        I18nUtil.checkCurrentLanguage();
        this.check4K();

        SwingUtilities.invokeLater(() -> {  // paint native blu svg in theme color behind scene
            AppMenubar.applyTheme(injectionModel.getMediatorUtils().getPreferencesUtil().getThemeFlatLafName());  // refresh missing components
            if (injectionModel.getMediatorUtils().getProxyUtil().isNotLive(GitUtil.ShowOnConsole.YES)) {  // network access
                return;
            }
            if (injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingUpdate()) {
                injectionModel.getMediatorUtils().getGitUtil().checkUpdate(GitUtil.ShowOnConsole.NO);
            }
            injectionModel.getMediatorUtils().getGitUtil().showNews();
            this.setVisible(true);
            MediatorHelper.panelAddressBar().getTextFieldAddress().requestFocusInWindow();  // required here to get focus
        });
    }

    private void initPaneComponents() {
        // Define the default panel: each component on a vertical line
        this.getContentPane().setLayout(new BoxLayout(this.getContentPane(), BoxLayout.PAGE_AXIS));

        this.tabManagers = new TabManagers();  // Tab manager linked to cards
        this.add(this.tabManagers);

        var menubar = new AppMenubar();
        this.setJMenuBar(menubar);
        MediatorHelper.register(menubar);

        var panelAddressBar = new PanelAddressBar();  // Textfield at the top
        MediatorHelper.register(panelAddressBar);
        this.add(panelAddressBar);

        var mainPanel = new JPanel(new BorderLayout());  // Main panel for tree and tables in the middle
        this.splitNS = new SplitNS();
        mainPanel.add(this.splitNS);
        this.add(mainPanel);

        menubar.getMenuWindows().switchLocaleFromPreferences();
    }

    private void initWindow() {
        this.setIconImages(UiUtil.getIcons());  // define small and large app icons
        var preferences = Preferences.userRoot().node(InjectionModel.class.getName());
        this.addWindowStateListener(e -> this.isMaximized = (e.getNewState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH);
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowOpened(WindowEvent event) {
                if (preferences.getBoolean(PreferencesUtil.IS_MAXIMIZED, false)) {
                    JFrameView.this.setExtendedState(Frame.MAXIMIZED_BOTH);
                }
                var horizontalTopBottomSplitter = preferences.getDouble(PreferencesUtil.NS_SPLIT, 0.75);
                if (!(0.0 <= horizontalTopBottomSplitter && horizontalTopBottomSplitter <= 1.0)) {
                    horizontalTopBottomSplitter = 0.75;
                }
                JFrameView.this.splitNS.setDividerLocation(horizontalTopBottomSplitter);
            }

            @Override
            public void windowClosing(WindowEvent e) {
                preferences.putBoolean(PreferencesUtil.IS_MAXIMIZED, JFrameView.this.isMaximized);
                preferences.putInt(
                    PreferencesUtil.EW_SPLIT,
                    // TODO not compatible arabic location
                    JFrameView.this.splitNS.getSplitEW().getDividerLocation()
                );
                
                var percentTopBottom = BigDecimal.valueOf(
                    JFrameView.this.splitNS.getDividerLocation() * 100.0
                    / JFrameView.this.splitNS.getHeight()
                    / 100
                );
                percentTopBottom = percentTopBottom.setScale(2, RoundingMode.HALF_UP);
                
                // Divider location change when window is maximized, we can't save getDividerLocation()
                preferences.putDouble(
                    PreferencesUtil.NS_SPLIT,
                    percentTopBottom.doubleValue() - 0.01  // Fix scale
                );

                preferences.putBoolean(PreferencesUtil.BINARY_VISIBLE, false);
                preferences.putBoolean(PreferencesUtil.CHUNK_VISIBLE, false);
                preferences.putBoolean(PreferencesUtil.NETWORK_VISIBLE, false);
                preferences.putBoolean(PreferencesUtil.JAVA_VISIBLE, false);
                for (var i = 0 ; i < MediatorHelper.tabConsoles().getTabCount() ; i++) {
                    if ("CONSOLE_BINARY_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
                        preferences.putBoolean(PreferencesUtil.BINARY_VISIBLE, true);
                    } else if ("CONSOLE_CHUNK_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
                        preferences.putBoolean(PreferencesUtil.CHUNK_VISIBLE, true);
                    } else if ("CONSOLE_NETWORK_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
                        preferences.putBoolean(PreferencesUtil.NETWORK_VISIBLE, true);
                    } else if ("CONSOLE_JAVA_LABEL".equals(MediatorHelper.tabConsoles().getTabComponentAt(i).getName())) {
                        preferences.putBoolean(PreferencesUtil.JAVA_VISIBLE, true);
                    }
                }
            }
        });
        
        this.setSize(1024, 768);
        this.setLocationRelativeTo(null);  // center the window
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    private void initShortcuts() {
        HotkeyUtil.addShortcut(this.getRootPane(), MediatorHelper.tabResults());
        HotkeyUtil.addTextFieldShortcutSelectAll();
    }

    public void resetInterface() {  // Empty the interface
        this.mapUuidShell.clear();
        MediatorHelper.panelAddressBar().getPanelTrailingAddress().reset();
        MediatorHelper.panelConsoles().reset();
        MediatorHelper.treeDatabase().reset();
        
        for (var i = 0 ; i < MediatorHelper.tabConsoles().getTabCount() ; i++) {
            var tabComponent = MediatorHelper.tabConsoles().getTabComponentAt(i);
            if (tabComponent != null) {
                tabComponent.setFont(tabComponent.getFont().deriveFont(Font.PLAIN));
            }
        }
        
        Stream.of(MediatorHelper.managerFile(), MediatorHelper.managerExploit()).forEach(managerList -> {
            managerList.setButtonEnable(false);
            managerList.changePrivilegeIcon(UiUtil.SQUARE.getIcon());
        });
    }

    private void displayVersion() {
        LOGGER.log(
            LogLevelUtil.CONSOLE_DEFAULT,
            "{} v{} on Java {}-{}-{}",
            () -> StringUtil.APP_NAME,
            () -> this.injectionModel.getPropertiesUtil().getVersionJsql(),
            () -> SystemUtils.JAVA_VERSION,
            () -> SystemUtils.OS_ARCH,
            () -> SystemUtils.USER_LANGUAGE
        );
    }

    private void check4K() {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int width = (int) screenSize.getWidth();
        if (width >= 3840 && !this.injectionModel.getMediatorUtils().getPreferencesUtil().is4K()) {
            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Screen compatible with resolution 4K, enable high-definition in Preferences");
        }
    }


    // Getters and setters

    public final Map<UUID, AbstractExploit> getMapUuidShell() {
        return this.mapUuidShell;
    }

    public SubscriberInteraction getSubscriber() {
        return this.subscriber;
    }

    public SplitNS getSplitNS() {
        return this.splitNS;
    }

    public TabManagers getTabManagers() {
        return this.tabManagers;
    }
}