Java - atualizando o código do cronômetro existente para incluir a contagem regressiva

O @HovercraftFullofEels teve a gentileza de me ajudar, fornecendo a base para o código a seguir, no qual eu fiz algumas modificações (marcadas pelos comentários "linha completamente adicionada" e pelo comentário gigante no final, que contém código a ser colocado em algum lugar no Arquivo).

O arquivo original era um cronômetro simples, e minhas modificações incluem 3 JTextFields. que levam minutos, segundos e CENTIsegundos (1 centisegundo = 1/100 de segundo). Também quero incluir um botão "Enviar", que permite ao programa ler a entrada desses três campos de texto. Eu escrevi o código para o método a ser chamado quando clicar em "Enviar" (incluído no comentário gigante no final). Ao clicar nele, quero que o programa inicie imediatamente uma contagem regressiva a partir desses valoresa partir do momento em que o cronômetro começou em vez de clicar no botão. Por exemplo, se o cronômetro estiver em execução por 20 minutos com o usuário clicar em "Enviar" com um tempo de entrada de 25 minutos, uma contagem regressiva de 5 minutos será iniciada.

Se isso ainda é confuso, tudo o que você realmente precisa saber é que meu método termina com uma linha que fornece uma representação em milissegundos de onde eu quero que a contagem regressiva comece; nesse ponto, eu quero que a contagem regressiva substitua o cronômetro. Também quero remover os botões "Pausar" e "Parar", mas não o botão "Iniciar" (você pensaria que eles seriam fáceis de remover, mas removi o que achava apropriado e recebi um erro ao compilar) e substitui com o único botão "Enviar".

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.Font;                     //line completely added
import javax.swing.*;

@SuppressWarnings("serial")
public class MyTimer2 extends JPanel implements GuiTimer {
    private static final String TIME_FORMAT = "%02d:%02d:%02d";   //changed from "%03d:%03d"
    private static final int EXTRA_WIDTH = 50;
    private JLabel timerLabel = new JLabel();
    private TimerControl timerControl = new TimerControl(this);

    JTextField minsField, secsField, centisField;     //line completely added - should this be private?
    JLabel colon, period;                             //line completely added - should this be private?

    public MyTimer2() {
        JPanel topPanel = new JPanel();
        topPanel.add(timerLabel);

        timerLabel.setFont(new Font("Arial", Font.BOLD, 64));     //line completely added

        minsField = new JTextField("", 2);
        secsField = new JTextField("", 2);
        centisField = new JTextField("", 2);

        colon = new JLabel(":");
        period = new JLabel(".");

        JPanel centerPanel = new JPanel();
        centerPanel.add(minsField);          //line completely added
        centerPanel.add(colon);              //line completely added
        centerPanel.add(secsField);          //line completely added
        centerPanel.add(period);             //line completely added
        centerPanel.add(centisField);        //line completely added

        JPanel bottomPanel = new JPanel();                              //line completely added
        bottomPanel.add(new JButton(timerControl.getStartAction()));    //changed from centerPanel
        bottomPanel.add(new JButton(timerControl.getStopAction()));     //changed from centerPanel

        setLayout(new BorderLayout());
        add(topPanel, BorderLayout.PAGE_START);
        add(centerPanel, BorderLayout.CENTER);
        add(bottomPanel, BorderLayout.PAGE_END);   //line completely added

        setDeltaTime(0);
    }

    @Override
    public void setDeltaTime(int delta) {
        int mins = (int) delta / 60000;                                          // line completely added
        int secs = ((int) delta % 60000) / 1000;                                 // %60000 added
        int centis = ((int) delta % 1000) / 10;                                  // / 10 added
        timerLabel.setText(String.format(TIME_FORMAT, mins, secs, centis));      // mins added; mSecs changed to centis
    }

    @Override
    public Dimension getPreferredSize() {
        Dimension superSz = super.getPreferredSize();
        if (isPreferredSizeSet()) {
            return superSz;
        }
        int prefW = superSz.width + EXTRA_WIDTH;
        int prefH = superSz.height;
        return new Dimension(prefW, prefH);
    }

    private static void createAndShowGui() {
        MyTimer2 mainPanel = new MyTimer2();

        JFrame frame = new JFrame("MyTimer2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  //changed from DISPOSE_ON_CLOSE
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}

interface GuiTimer {
    public abstract void setDeltaTime(int delta);
}

@SuppressWarnings("serial")
class TimerControl {
    private static final int TIMER_DELAY = 10;
    private long startTime = 0;
    private long pauseTime = 0;
    private Timer timer;
    private GuiTimer gui;
    private StartAction startAction = new StartAction();
    private StopAction stopAction = new StopAction();

    public TimerControl(GuiTimer gui) {
        this.gui = gui;
    }

    public Action getStopAction() {
        return stopAction;
    }

    public Action getStartAction() {
        return startAction;
    }

    enum State {
        START("Start", KeyEvent.VK_S), 
        PAUSE("Pause", KeyEvent.VK_P);
        private String text;
        private int mnemonic;

        private State(String text, int mnemonic) {
            this.text = text;
            this.mnemonic = mnemonic;
        }

        public String getText() {
            return text;
        }

        public int getMnemonic() {
            return mnemonic;
        }
    };

    private class StartAction extends AbstractAction {
        private State state;

        public StartAction() {
            setState(State.START);
        }

        public final void setState(State state) {
            this.state = state;
            putValue(NAME, state.getText());
            putValue(MNEMONIC_KEY, state.getMnemonic());
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (state == State.START) {
                if (timer != null && timer.isRunning()) {
                    return; // the timer's already running
                }
                setState(State.PAUSE);
                if (startTime <= 0) {
                    startTime = System.currentTimeMillis();
                    timer = new Timer(TIMER_DELAY, new TimerListener());
                } else {
                    startTime += System.currentTimeMillis() - pauseTime;
                }
                timer.start();
            } else if (state == State.PAUSE) {
                setState(State.START);
                pauseTime = System.currentTimeMillis();
                timer.stop();
            }
        }
    }

    private class StopAction extends AbstractAction {
        public StopAction() {
            super("Stop");
            int mnemonic = KeyEvent.VK_T;
            putValue(MNEMONIC_KEY, mnemonic);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (timer == null) {
                return;
            }
            timer.stop();
            startAction.setState(State.START);
            startTime = 0;
        }
    }

    private class TimerListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            long time = System.currentTimeMillis();
            long delta = time - startTime;
            gui.setDeltaTime((int) delta); 
        }
    }

}

/*not sure where this will go, but this is the code for clicking "Submit"   

   //upon clicking "Submit"...
   public void actionPerformed(ActionEvent e)
   {      
      String minsStr = minsField.getText();
      String secsStr = secsField.getText();
      String centisStr = centisField.getText();
      int minsInput = Integer.parseInt(minsStr);
      int secsInput = Integer.parseInt(secsStr);
      int centisInput = Integer.parseInt(centisStr);

      long millis = minsInput * 60000 + secsInput * 1000 + centisInput * 10;

      long millisCountdown = millis - delta;   //where "delta" is elapsed milliseconds

      if(millisCountdown < 0)
          JOptionPane.showMessageDialog("Invalid time entered.");

      else
          //then immediately change from stopwatch to countdown beginning from millisCountdown and ending at 00:00:00

      minsField.setText("");     //clear minsField
      secsField.setText("");     //clear secsField
      centisField.setText("");   //clear centisField
   }
*/

Se alguém pudesse me ajudar com isso, eu agradeceria muito. Infelizmente eu não entendo a maioria do código do Hovercraft, então não tenho idéia para onde ir depois do que já fiz.

Obrigado!

EDIT: Aqui está a versão atualizada do código do @ MadProgrammer:

import java.awt.EventQueue;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Font;
import java.time.Duration;
import java.time.LocalTime;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class StopWatch {

    public static void main(String[] args) {
        new StopWatch();
    }

    public StopWatch() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class TestPane extends JPanel {

        protected static final String TIME_FORMAT = "%02d:%02d.%02d";

        private LocalTime startTime;
        private LocalTime targetTime;

        private JLabel label;
        private JTextField minsField, secsField, centisField;
        private JButton start, submit;

        private Timer timer;

        public TestPane() {
            JPanel topRow = new JPanel();
            JPanel centerRow = new JPanel();
            JPanel bottomRow = new JPanel();

            label = new JLabel(formatDuration(Duration.ofMillis(0)));
            topRow.add(label);

            minsField = new JTextField("", 2);
            secsField = new JTextField("", 2);
            centisField = new JTextField("", 2);
            centerRow.add(minsField);
            centerRow.add(secsField);
            centerRow.add(centisField);

            start = new JButton("Start");
            start.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (!timer.isRunning()) {
                        startTime = LocalTime.now();
                        timer.start();
                    }
                }
            });

            bottomRow.add(start);

            submit = new JButton("Submit");
            submit.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                if (timer.isRunning()) {
                        timer.stop();

                        Duration runningTime = Duration.between(startTime, LocalTime.now());
                        // Subtract the required amount of time from the duration

                        String minsStr = minsField.getText();
                        String secsStr = secsField.getText();
                        String centisStr = centisField.getText();

                        if(minsStr.matches("\\d+$") && secsStr.matches("\\d+$") && centisStr.matches("\\d+$"))
                        {                       
                           int minsInput = Integer.parseInt(minsStr);
                           int secsInput = Integer.parseInt(secsStr);
                           int centisInput = Integer.parseInt(centisStr);

                           if(minsInput >= 0 && secsInput >= 0 && secsInput < 60 && centisInput >= 0 && centisInput < 100)
                           {

                              long millis = minsInput * 60000 + secsInput * 1000 + centisInput * 10;

                              runningTime = runningTime.minusMillis(millis);

                              timer.start();

                              // No negative times
                              if (runningTime.toMillis() > 0)
                              {
                                  // When the timer is to end...
                                  targetTime = LocalTime.now().plus(runningTime);
                              }

                              else
                              {
                                 JOptionPane.showMessageDialog(null, "Invalid time entered.");
                              }
                           }

                           else
                           {
                              timer.start();
                              JOptionPane.showMessageDialog(null, "Invalid time entered.");
                           }
                        }

                        else
                        {
                           timer.start();
                           JOptionPane.showMessageDialog(null, "Invalid time entered.");
                        }

                        minsField.setText("");
                        secsField.setText("");
                        centisField.setText("");
                    }
                }
            });

            bottomRow.add(submit);

            timer = new Timer(10, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    if (targetTime != null) {
                        Duration duration = Duration.between(LocalTime.now(), targetTime);
                        if (duration.toMillis() <= 0) {
                            duration = Duration.ofMillis(0);
                            timer.stop();
                            targetTime = null;
                        }
                        label.setText(formatDuration(duration));
                    } else {
                        // Count up...
                        Duration duration = Duration.between(startTime, LocalTime.now());
                        label.setText(formatDuration(duration));
                    }
                }
            });

            setLayout(new BorderLayout());

            label.setFont(new Font("Arial", Font.BOLD, 64));

            add(topRow, BorderLayout.PAGE_START);
            add(centerRow, BorderLayout.CENTER);
            add(bottomRow, BorderLayout.PAGE_END);
        }

        protected String formatDuration(Duration duration) {
            long mins = duration.toMinutes();
            duration = duration.minusMinutes(mins);
            long seconds = duration.toMillis() / 1000;
            duration = duration.minusSeconds(seconds);
            long centis = duration.toMillis() / 10;

            return String.format(TIME_FORMAT, mins, seconds, centis);
        }
    }
}

questionAnswers(1)

yourAnswerToTheQuestion