StatusBar.java

package org.egothor.methodatlas.gui.panel;

import org.egothor.methodatlas.gui.model.AnalysisModel;

import javax.swing.*;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import java.awt.*;
import java.beans.PropertyChangeEvent;

/**
 * Slim single-row panel displayed at the very bottom of the main window.
 *
 * <p>The bar has three zones:</p>
 * <ul>
 *   <li><strong>Left</strong> — human-readable status message reflecting the
 *       phase and the class currently being processed (for example
 *       {@code "AI enrichment [3/42] — FooTest"})</li>
 *   <li><strong>Centre</strong> — progress bar: indeterminate during the
 *       file-system scan; determinate (with a {@code "X / Y"} string
 *       overlay) during the AI enrichment phase; hidden when idle</li>
 *   <li><strong>Right</strong> — running totals of discovered methods and
 *       classes, for example {@code "42 methods | 7 classes"}</li>
 * </ul>
 *
 * <p>This component registers itself as a {@link java.beans.PropertyChangeListener}
 * on the supplied {@link AnalysisModel} and must therefore be created on the
 * Swing Event Dispatch Thread.</p>
 *
 * @see ActivityPanel
 */
public final class StatusBar extends JPanel {

    @java.io.Serial
    private static final long serialVersionUID = 1L;

    private final JLabel messageLabel = new JLabel("Ready");
    private final JProgressBar progressBar = new JProgressBar();
    private final JLabel countLabel = new JLabel();

    /**
     * Constructs the status bar and registers it as a property-change
     * listener on {@code model}.
     *
     * <p>Must be called on the Swing Event Dispatch Thread.</p>
     *
     * @param model model whose status, progress, entry-count, and clear
     *              events drive this panel's display; must not be
     *              {@code null}
     */
    public StatusBar(AnalysisModel model) {
        super();
        setLayout(new BorderLayout(8, 0));
        setBorder(new CompoundBorder(
                new MatteBorder(1, 0, 0, 0, UIManager.getColor("Separator.foreground")),
                new EmptyBorder(4, 8, 4, 8)));

        progressBar.setStringPainted(false);
        progressBar.setPreferredSize(new Dimension(180, 14));
        progressBar.setVisible(false);

        countLabel.setForeground(UIManager.getColor("Label.disabledForeground"));
        countLabel.setFont(countLabel.getFont().deriveFont(Font.PLAIN, 11f));

        add(messageLabel, BorderLayout.WEST);
        add(progressBar, BorderLayout.CENTER);
        add(countLabel, BorderLayout.EAST);

        model.addPropertyChangeListener(this::onModelChange);
    }

    private void onModelChange(PropertyChangeEvent evt) {
        switch (evt.getPropertyName()) {
            case "status" -> {
                AnalysisModel.Status s = (AnalysisModel.Status) evt.getNewValue();
                boolean busy = s == AnalysisModel.Status.SCANNING
                        || s == AnalysisModel.Status.AI_RUNNING;
                progressBar.setVisible(busy);
                if (s == AnalysisModel.Status.SCANNING) {
                    progressBar.setIndeterminate(true);
                    progressBar.setStringPainted(false);
                } else if (s == AnalysisModel.Status.AI_RUNNING) {
                    progressBar.setIndeterminate(false);
                    progressBar.setStringPainted(true);
                } else {
                    progressBar.setIndeterminate(false);
                    progressBar.setStringPainted(false);
                }
            }
            case "statusMessage" -> messageLabel.setText((String) evt.getNewValue());
            case "progress" -> {
                AnalysisModel src = (AnalysisModel) evt.getSource();
                int current = src.getProgressCurrent();
                int total = src.getProgressTotal();
                if (total > 0) {
                    progressBar.setMaximum(total);
                    progressBar.setValue(current);
                    progressBar.setString(current + " / " + total);
                }
            }
            case "entries" -> {
                AnalysisModel src = (AnalysisModel) evt.getSource();
                countLabel.setText(src.getTotalMethodCount() + " methods | "
                        + src.getClassCount() + " classes");
            }
            case "cleared" -> {
                countLabel.setText("");
                progressBar.setValue(0);
                progressBar.setString(null);
            }
            default -> { /* ignore */ }
        }
    }
}