SquirrelJME/SquirrelJME

View on GitHub
tools/squirreljme-debugger/src/main/java/cc/squirreljme/debugger/PrimaryFrame.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// Multi-Phasic Applications: SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.debugger;

import cc.squirreljme.jdwp.JDWPCommandSet;
import cc.squirreljme.jdwp.JDWPCommandSetVirtualMachine;
import cc.squirreljme.jdwp.JDWPId;
import cc.squirreljme.jdwp.JDWPIdKind;
import cc.squirreljme.jdwp.JDWPPacket;
import cc.squirreljme.jdwp.JDWPStepDepth;
import cc.squirreljme.jdwp.JDWPStepSize;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.WindowConstants;
import javax.swing.filechooser.FileNameExtensionFilter;
import net.multiphasicapps.classfile.ClassFile;
import net.multiphasicapps.classfile.ClassName;
import net.multiphasicapps.classfile.FieldDescriptor;

/**
 * Primary display window.
 *
 * @since 2024/01/19
 */
public class PrimaryFrame
    extends JFrame
{
    /** The debugger state. */
    protected final DebuggerState state;
    
    /** The status panel. */
    protected final StatusPanel statusPanel;
    
    /** The toolbar for common actions. */
    protected final JToolBar toolBar;
    
    /** Thread view. */
    protected final ShownThreads shownThreads;
    
    /** Currently shown context. */
    protected final ContextThreadFrame context;
    
    /** The shown context thread. */
    protected final ShownContextMethod shownContext;
    
    /** Debugger preferences. */
    protected final Preferences preferences;
    
    /**
     * Initializes the primary frame.
     *
     * @param __state The main debugging state.
     * @param __preferences Preferences for the debugger.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/19
     */
    public PrimaryFrame(DebuggerState __state, Preferences __preferences)
        throws NullPointerException
    {
        if (__state == null || __preferences == null)
            throw new NullPointerException("NARG");
        
        // Store state for later usage
        this.state = __state;
        this.context = __state.context;
        this.preferences = __preferences;
        
        // To keep it more easily workable
        this.setMinimumSize(new Dimension(640, 480));
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        
        // Set window icon
        Utils.setIcon(this);
        
        // Insignia
        this.setTitle("SquirrelJME Debugger");
        
        // Default shortcut key mask, like Apple's Command button
        int metaMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        
        // Preferences
        JMenuItem preferencesItem = new JMenuItem("Preferences...");
        preferencesItem.setMnemonic('P');
        preferencesItem.setAccelerator(
            KeyStroke.getKeyStroke(Character.valueOf('s'), 
                InputEvent.ALT_DOWN_MASK | metaMask));
        preferencesItem.addActionListener((__event) -> {
            PreferencesDialog prefs = new PreferencesDialog(this,
                this.preferences);
            prefs.setLocationRelativeTo(null);
            prefs.setVisible(true);
        });
        
        // About the debugger
        JMenuItem aboutItem = new JMenuItem("About...");
        aboutItem.setMnemonic('A');
        aboutItem.setIcon(new ImageIcon(Utils.lexIcon()));
        aboutItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
        aboutItem.addActionListener((__event) -> {
            AboutDialog about = new AboutDialog(this);
            about.setLocationRelativeTo(null);
            about.setVisible(true);
        });
        
        // Exit
        JMenuItem exitItem = PrimaryFrame.__menuItem(
            "Exit", "system-log-out");
        exitItem.setMnemonic('x');
        exitItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK));
        
        // File menu
        JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic('F');
        fileMenu.add(preferencesItem);
        fileMenu.addSeparator();
        fileMenu.add(aboutItem);
        fileMenu.addSeparator();
        fileMenu.add(exitItem);
        
        // Refresh
        JMenuItem refreshItem = PrimaryFrame.__menuItem(
            "Refresh", "view-refresh");
        refreshItem.setMnemonic('R');
        refreshItem.addActionListener(this::__refresh);
        refreshItem.setAccelerator(
            KeyStroke.getKeyStroke(Character.valueOf('r'), metaMask));
        
        // Capabilities view
        JMenuItem capsItem = new JMenuItem("Capabilities");
        capsItem.setMnemonic('C');
        capsItem.addActionListener(this::__inspectCapabilities);
        
        // Object view
        JMenuItem objectItem = new JMenuItem("Object");
        objectItem.setMnemonic('O');
        objectItem.setAccelerator(
            KeyStroke.getKeyStroke(Character.valueOf('o'), metaMask));
        
        // Type view
        JMenuItem typeItem = new JMenuItem("Type");
        typeItem.setMnemonic('y');
        typeItem.setAccelerator(
            KeyStroke.getKeyStroke(Character.valueOf('y'), metaMask));
        typeItem.addActionListener(this::__inspectType);
        
        // Thread group
        JMenuItem threadGroupItem = new JMenuItem("Thread Group");
        threadGroupItem.setMnemonic('G');
        threadGroupItem.setAccelerator(
            KeyStroke.getKeyStroke(Character.valueOf('g'), metaMask));
        
        // Thread
        JMenuItem threadItem = new JMenuItem("Thread");
        threadItem.setMnemonic('T');
        threadItem.setAccelerator(
            KeyStroke.getKeyStroke(Character.valueOf('t'), metaMask));
        threadItem.addActionListener(this::__inspectThread);
        
        // View menu
        JMenu viewMenu = new JMenu("View");
        viewMenu.setMnemonic('V');
        viewMenu.add(refreshItem);
        viewMenu.add(capsItem);
        viewMenu.add(objectItem);
        viewMenu.add(typeItem);
        viewMenu.add(threadGroupItem);
        viewMenu.add(threadItem);
        
        // Resume items
        JMenuItem resumeSingleItem = PrimaryFrame.__menuItem(
            "Resume Single Thread", "media-playback-start");
        resumeSingleItem.addActionListener(this::__threadSingleResume);
        resumeSingleItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0));
        JMenuItem suspendSingleItem = PrimaryFrame.__menuItem(
            "Suspend Single Thread", "media-playback-pause");
        suspendSingleItem.addActionListener(this::__threadSingleSuspend);
        suspendSingleItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F5, 
                InputEvent.SHIFT_DOWN_MASK));
        
        JMenuItem resumeAllItem = PrimaryFrame.__menuItem(
            "Resume All Threads", "weather-clear");
        resumeAllItem.addActionListener(this::__threadAllResume);
        resumeAllItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F9,
                InputEvent.CTRL_DOWN_MASK));
        JMenuItem suspendAllItem = PrimaryFrame.__menuItem(
            "Suspend All Threads", "weather-snow");
        suspendAllItem.addActionListener(this::__threadAllSuspend);
        suspendAllItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F5, 
                InputEvent.SHIFT_DOWN_MASK |
                    InputEvent.CTRL_DOWN_MASK));
        
        JMenuItem singleStepIntoItem = PrimaryFrame.__menuItem(
            "Single Step Into", "go-down");
        singleStepIntoItem.addActionListener(this::__singleStepMinInto);
        singleStepIntoItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F7, 0));
        JMenuItem singleStepOverItem = PrimaryFrame.__menuItem(
            "Single Step Over", "go-jump");
        singleStepOverItem.addActionListener(this::__singleStepMinOver);
        singleStepOverItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0));
        JMenuItem singleStepOutItem = PrimaryFrame.__menuItem(
            "Single Step Out", "go-top");
        singleStepOutItem.addActionListener(this::__singleStepMinOut);
        singleStepOutItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F8,
                InputEvent.SHIFT_DOWN_MASK));
        
        JMenuItem singleLineIntoItem = PrimaryFrame.__menuItem(
            "Single Line Step Into", "format-indent-more");
        singleLineIntoItem.addActionListener(this::__singleStepLineInto);
        singleLineIntoItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F7,
                InputEvent.CTRL_DOWN_MASK));
        JMenuItem singleLineOverItem = PrimaryFrame.__menuItem(
            "Single Line Step Over", "format-justify-center");
        singleLineOverItem.addActionListener(this::__singleStepLineOver);
        singleLineOverItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F8,
                InputEvent.CTRL_DOWN_MASK));
        JMenuItem singleLineOutItem = PrimaryFrame.__menuItem(
            "Single Line Step Out", "edit-undo");
        singleLineOutItem.addActionListener(this::__singleStepLineOut);
        singleLineOutItem.setAccelerator(
            KeyStroke.getKeyStroke(KeyEvent.VK_F8,
                InputEvent.CTRL_DOWN_MASK |
                    InputEvent.SHIFT_DOWN_MASK));
        
        // Debug Controls
        JMenu debugMenu = new JMenu("Debug");
        debugMenu.setMnemonic('D');
        debugMenu.add(resumeSingleItem);
        debugMenu.add(suspendSingleItem);
        debugMenu.addSeparator();
        debugMenu.add(resumeAllItem);
        debugMenu.add(suspendAllItem);
        debugMenu.addSeparator();
        debugMenu.add(singleStepIntoItem);
        debugMenu.add(singleStepOverItem);
        debugMenu.add(singleStepOutItem);
        debugMenu.addSeparator();
        debugMenu.add(singleLineIntoItem);
        debugMenu.add(singleLineOverItem);
        debugMenu.add(singleLineOutItem);
        
        // Probe vendor specific commands
        JMenuItem vendorProbeItem = new JMenuItem(
            "Probe Vendor Specific Commands");
        vendorProbeItem.setMnemonic('p');
        vendorProbeItem.addActionListener(this::__probeVendorCommands);
        
        // Advanced Menu
        JMenu advancedMenu = new JMenu("Advanced");
        advancedMenu.add(vendorProbeItem);
        
        // Menu bar
        JMenuBar mainMenu = new JMenuBar();
        mainMenu.add(fileMenu);
        mainMenu.add(viewMenu);
        mainMenu.add(debugMenu);
        mainMenu.add(advancedMenu);
        
        // Use the menu finally
        this.setJMenuBar(mainMenu);
        
        // Show thread view on the left
        ShownThreads shownThreads = new ShownThreads(__state, this.context);
        shownThreads.setMinimumSize(new Dimension(200, 300));
        shownThreads.setPreferredSize(new Dimension(200, 300));
        shownThreads.setMaximumSize(new Dimension(200, 9999));
        this.add(shownThreads, BorderLayout.LINE_START);
        this.shownThreads = shownThreads;
        
        // Context viewer for the current frame location
        ShownContextMethod shownContext = new ShownContextMethod(
            this.state, this.context, __preferences);
        this.shownContext = shownContext;
        this.add(shownContext, BorderLayout.CENTER);
        
        // Setup toolbar
        JToolBar toolBar = new JToolBar();
        this.toolBar = toolBar;
        
        // Do not allow this to float
        toolBar.setFloatable(false);
        
        JButton viewClassDisk = PrimaryFrame.__barButton(toolBar,
            "View Class From Disk", "document-open");
        viewClassDisk.addActionListener(this::__viewClassDisk);
        
        JButton viewClassLocal = PrimaryFrame.__barButton(toolBar,
            "View Class From Remote", "computer");
        viewClassLocal.addActionListener(this::__viewClassLocal);
        
        JButton viewClassNet = PrimaryFrame.__barButton(toolBar,
            "View Class From Remote", "network-receive");
        viewClassNet.addActionListener(this::__viewClassNetwork);
        
        toolBar.addSeparator();
        
        // Refresh
        JButton refresh = PrimaryFrame.__barButton(toolBar,
            "Refresh", "view-refresh");
        refresh.addActionListener(this::__refresh);
        
        toolBar.addSeparator();
        
        JButton resumeSingle = PrimaryFrame.__barButton(toolBar,
            "Resume Single Thread", "media-playback-start");
        resumeSingle.addActionListener(this::__threadSingleResume);
        JButton suspendSingle = PrimaryFrame.__barButton(toolBar,
            "Suspend Single Thread", "media-playback-pause");
        suspendSingle.addActionListener(this::__threadSingleSuspend);
        
        toolBar.addSeparator();
        
        JButton resumeAll = PrimaryFrame.__barButton(toolBar,
            "Resume All Threads", "weather-clear");
        resumeAll.addActionListener(this::__threadAllResume);
        JButton suspendAll = PrimaryFrame.__barButton(toolBar,
            "Suspend All Threads", "weather-snow");
        suspendAll.addActionListener(this::__threadAllSuspend);
        
        toolBar.addSeparator();
        
        JButton singleStepInto = PrimaryFrame.__barButton(toolBar,
            "Single Step Into", "go-down");
        singleStepInto.addActionListener(this::__singleStepMinInto);
        JButton singleStepOver = PrimaryFrame.__barButton(toolBar,
            "Single Step Over", "go-jump");
        singleStepOver.addActionListener(this::__singleStepMinOver);
        JButton singleStepOut = PrimaryFrame.__barButton(toolBar,
            "Single Step Out", "go-top");
        singleStepOut.addActionListener(this::__singleStepMinOut);
        
        toolBar.addSeparator();
        
        JButton singleLineStepInto = PrimaryFrame.__barButton(toolBar,
            "Single Line Step Into", "format-indent-more");
        singleLineStepInto.addActionListener(this::__singleStepLineInto);
        JButton singleLineStepOver = PrimaryFrame.__barButton(toolBar,
            "Single Line Step Over", "format-justify-center");
        singleLineStepOver.addActionListener(this::__singleStepLineOver);
        JButton singleLineStepOut = PrimaryFrame.__barButton(toolBar,
            "Single Line Step Out", "edit-undo");
        singleLineStepOut.addActionListener(this::__singleStepLineOut);
        
        // Add to the top
        this.add(toolBar, BorderLayout.PAGE_START);
        
        // Setup status panel
        StatusPanel statusPanel = new StatusPanel(__state);
        this.statusPanel = statusPanel;
        
        // Add that to the bottom
        this.add(statusPanel, BorderLayout.PAGE_END);
        
        // Add a timer to keep everything updated accordingly
        Timer updateTimer = new Timer(30_000,
            (__ignored) -> this.update());
        updateTimer.start();
    }
    
    /**
     * Update via the event handler.
     *
     * @param __state The state used.
     * @param __event The debugging event.
     * @since 2024/01/26
     */
    public void handleSingleStep(DebuggerState __state,
        SingleStepEvent __event)
    {
        // Note it
        Debugging.debugNote("Single stepped.");
        this.statusPanel.setMessage("Single stepped.");
        
        // Update context frame from this event, since we are now in a new
        // location
        InfoThread inThread = __event.thread;
        if (inThread != null)
            inThread.frames.update(__state, (__ignored, __value) -> {
                InfoFrame[] frames = __value.get();
                if (frames != null && frames.length > 0)
                    this.context.set(frames[0]);
                
                this.update();
            });
        
        // Update information
        this.update();
    }
    
    /**
     * General update.
     *
     * @since 2024/01/26
     */
    public void update()
    {
        Utils.swingInvoke(() -> {
            // Needs to update second as the thread could have changed
            this.shownContext.update();
        });
    }
    
    /**
     * Opens the inspector selection.
     *
     * @param __stored The stored items to look at.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/20
     */
    private void __inspect(StoredInfo<?> __stored)
        throws NullPointerException
    {
        Utils.inspect(this, this.state, __stored);
    }
    
    /**
     * Shows the capabilities of the connected virtual machine.
     *
     * @param __event Not used.
     * @since 2024/01/19
     */
    private void __inspectCapabilities(ActionEvent __event)
    {
        InspectCapabilities inspect = new InspectCapabilities(this,
            this.state.capabilities);
        
        // Show it
        inspect.setLocationRelativeTo(null);
        inspect.setVisible(true);
    }
    
    /**
     * Opens the thread inspector.
     *
     * @param __event Not used.
     * @since 2024/01/20
     */
    private void __inspectThread(ActionEvent __event)
    {
        this.__inspect(this.state.storedInfo.getThreads());
    }
    
    /**
     * Inspects the given type.
     *
     * @param __action Not used.
     * @since 2024/01/23
     */
    private void __inspectType(ActionEvent __action)
    {
        // Get every single loaded class because this is really expected
        DebuggerState state = this.state;
        try (JDWPPacket out = state.request(JDWPCommandSet.VIRTUAL_MACHINE,
            JDWPCommandSetVirtualMachine.ALL_CLASSES))
        {
            // Wait for response
            state.send(out,
                (__state, __reply) -> {
                    // We need to store the actual classes
                    StoredInfo<InfoClass> classes =
                        this.state.storedInfo.getClasses();
                
                    // Read in all the classes
                    int count = __reply.readInt();
                    for (int i = 0; i < count; i++)
                    {
                        // Ignore tag
                        __reply.readByte();
                        
                        // Read the type ID
                        JDWPId typeId = __reply.readId(
                            JDWPIdKind.REFERENCE_TYPE_ID);
                        
                        // Read name
                        String name = __reply.readString();
                        
                        // Ignore status
                        __reply.readInt();
                        
                        // Setup class
                        InfoClass infoClass = classes.get(__state, typeId);
                        if (infoClass != null)
                            infoClass.thisName.set(
                                new FieldDescriptor(name).className());
                    }
                    
                    this.__inspect(state.storedInfo.getClasses());
                }, ReplyHandler.IGNORED);
        }
    }
    
    /**
     * Views a class stored on the disk.
     *
     * @param __event The event.
     * @since 2024/01/22
     */
    private void __viewClassDisk(ActionEvent __event)
    {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileFilter(new FileNameExtensionFilter(
            "Java Class Files", "class"));
        
        if (fileChooser.showOpenDialog(this) ==
            JFileChooser.APPROVE_OPTION)
        {
            try (InputStream in = Files.newInputStream(
                fileChooser.getSelectedFile().toPath(),
                StandardOpenOption.READ))
            {
                // Decode the class
                ClassFile classFile = ClassFile.decode(in);
                
                // Use standard viewer
                this.__viewClass(new JavaClassViewer(classFile));
            }
            catch (IOException __e)
            {
                Utils.throwableTraceDialog(this,
                    "Failed to load class from disk", __e);
            }
        }
    }
    
    /**
     * Views the given class.
     *
     * @param __viewer The class to view.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/22
     */
    private void __viewClass(ClassViewer __viewer)
        throws NullPointerException
    {
        if (__viewer == null)
            throw new NullPointerException("NARG");
        
        ShownClassDialog dialog = new ShownClassDialog(this,
            this.state, __viewer);
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);
    }
    
    /**
     * Views a local class.
     *
     * @param __event Not used.
     * @since 2024/01/29
     */
    private void __viewClassLocal(ActionEvent __event)
    {
        String option = (String)JOptionPane.showInputDialog(
            this,
            "Choose local class",
            "Choose local class",
            JOptionPane.QUESTION_MESSAGE,
            null,
            null,
            "java/lang/Class");
        
        // Was a class specified?
        if (option != null)
        {
            // If there are dots, it really should be slashes
            if (option.indexOf('.') >= 0)
                option = option.replace('.', '/');
            
            // Perform lookup
            ClassName className = new ClassName(option);
            ClassFile[] classFiles = Utils.loadClass(className,
                this.preferences);
            
            // No class?
            if (classFiles == null || classFiles.length == 0)
            {
                Utils.throwableTraceDialog(this,
                    "Could not find class: " + className,
                    new Throwable());
                return;
            }
            
            // What do we look at?
            ClassFile lookAt;
            if (classFiles.length == 1)
                lookAt = classFiles[0];
            else
                lookAt = (ClassFile)JOptionPane.showInputDialog(
                    this,
                    "Select class:",
                    "Multiple classes found",
                    JOptionPane.QUESTION_MESSAGE,
                    null,
                    classFiles,
                    classFiles[0]);
            
            // Look at the given class
            if (lookAt != null)
                this.__viewClass(new JavaClassViewer(lookAt));
        }
    }
    
    /**
     * Views a class from the remote virtual machine.
     *
     * @param __event The event.
     * @since 2024/01/22
     */
    private void __viewClassNetwork(ActionEvent __event)
    {
        String option = (String)JOptionPane.showInputDialog(
            this,
            "Choose remote class",
            "Choose remote class",
            JOptionPane.QUESTION_MESSAGE,
            null,
            null,
            "java/lang/Class");
        
        // Was a class specified?
        if (option != null)
        {
            // If there are dots, it really should be slashes
            if (option.indexOf('.') >= 0)
                option = option.replace('.', '/');
            
            // Perform lookup
            ClassName className = new ClassName(option);
            this.state.lookupClass(className, (__info) -> {
                InfoClass lookAt;
                if (__info.length == 1)
                    lookAt = __info[0];
                else
                    lookAt = (InfoClass)JOptionPane.showInputDialog(
                        this,
                        "Select class:",
                        "Multiple classes found",
                        JOptionPane.QUESTION_MESSAGE,
                        null,
                        __info,
                        __info[0]);
                
                // Look at the given class
                if (lookAt != null)
                    this.__viewClass(new RemoteClassViewer(
                        this.state, lookAt));
            }, (__e) -> {
                Utils.throwableTraceDialog(this,
                    "Could not find class: " + className, __e);
            });
        }
    }
    
    /**
     * Probes for vendor specific commands in the remote virtual machine.
     *
     * @param __event Ignored.
     * @since 2024/01/28
     */
    private void __probeVendorCommands(ActionEvent __event)
    {
        // Really mean this
        if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
            this,
            "This probes all 32768 vendor specific JDWP commands\n" +
                "which may overload the JVM and/or cause it to crash\n" +
                "and/or have undesired effects.\n" +
                "You have been warned, continue?",
            "Are you sure?",
            JOptionPane.YES_NO_OPTION,
            JOptionPane.QUESTION_MESSAGE,
            null))
        {
            VendorCommandProbe probe = new VendorCommandProbe(this,
                this.state);
            probe.setLocationRelativeTo(null);
            probe.setVisible(true);
        }
    }
    
    /**
     * Refreshes the view.
     *
     * @param __event Ignored.
     * @since 2024/01/28
     */
    private void __refresh(ActionEvent __event)
    {
        Utils.swingInvoke(() -> {
            this.update();
        });
    }
    
    /**
     * Line single step into.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __singleStepLineInto(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadStep(thread, 1,
                JDWPStepDepth.INTO, JDWPStepSize.LINE,
                this::handleSingleStep);
        
        // Update everything
        this.update();
    }
    
    /**
     * Line single step out.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __singleStepLineOut(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadStep(thread, 1,
                JDWPStepDepth.OUT, JDWPStepSize.LINE,
                this::handleSingleStep);
        
        // Update everything
        this.update();
    }
    
    /**
     * Line single step over.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __singleStepLineOver(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadStep(thread, 1,
                JDWPStepDepth.OVER, JDWPStepSize.LINE,
                this::handleSingleStep);
        
        // Update everything
        this.update();
    }
    
    /**
     * Minimal single step into.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __singleStepMinInto(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadStep(thread, 1,
                JDWPStepDepth.INTO, JDWPStepSize.MIN,
                this::handleSingleStep);
        
        // Update everything
        this.update();
    }
    
    /**
     * Minimal single step out.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __singleStepMinOut(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadStep(thread, 1,
                JDWPStepDepth.OUT, JDWPStepSize.MIN,
                this::handleSingleStep);
        
        // Update everything
        this.update();
    }
    
    /**
     * Minimal single step over.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __singleStepMinOver(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadStep(thread, 1,
                JDWPStepDepth.OVER, JDWPStepSize.MIN,
                this::handleSingleStep);
        
        // Update everything
        this.update();
    }
    
    /**
     * Resumes all threads.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __threadAllResume(ActionEvent __event)
    {
        this.state.threadResumeAll(() -> {
            this.update();
        });
    }
    
    /**
     * Suspends all threads.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __threadAllSuspend(ActionEvent __event)
    {
        this.state.threadSuspendAll(() -> {
            this.update();
        });
    }
    
    /**
     * Resumes a single thread.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __threadSingleResume(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadResume(thread, () -> {
                // Drop all frames and have no context
                thread.frames.drop();
                this.context.dropFrame(thread);
                
                // Update the UI
                this.update();
            });
    }
    
    /**
     * Suspends a single thread.
     *
     * @param __event Ignored.
     * @since 2024/01/27
     */
    private void __threadSingleSuspend(ActionEvent __event)
    {
        InfoThread thread = this.context.getThread();
        if (thread != null)
            this.state.threadSuspend(thread, () -> {
                this.update();
            });
    }
    
    /**
     * Creates a toolbar button.
     *
     * @param __label The label to use.
     * @param __tango The tango icon to use.
     * @return The created button.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/21
     */
    private static JButton __barButton(String __label, String __tango)
        throws NullPointerException
    {
        if (__label == null || __tango == null)
            throw new NullPointerException("NARG");
        
        JButton button = new JButton();
        
        button.setToolTipText(__label);
        button.setIcon(Utils.tangoIcon(__tango));
        
        return button;
    }
    
    /**
     * Creates a toolbar button and adds it to the given toolbar.
     *
     * @param __toolBar The toolbar to add to.
     * @param __label The label to use.
     * @param __tango The tango icon to use.
     * @return The created button.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/21
     */
    private static JButton __barButton(JToolBar __toolBar, String __label,
        String __tango)
        throws NullPointerException
    {
        if (__toolBar == null)
            throw new NullPointerException("NARG");
        
        JButton button = PrimaryFrame.__barButton(__label, __tango);
        
        __toolBar.add(button);
        
        return button;
    }
    
    /**
     * Creates a menu item.
     *
     * @param __label The label to use.
     * @param __tango The tango icon to use.
     * @return The created button.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/21
     */
    private static JMenuItem __menuItem(String __label, String __tango)
        throws NullPointerException
    {
        if (__label == null || __tango == null)
            throw new NullPointerException("NARG");
        
        return new JMenuItem(__label,
            Utils.tangoIcon(__tango));
    }
}