// Import statements for the JComponents -- the visual widgets that will be
// employed
import javax.swing.JLabel;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JTextField;

// Import statement for the layout manager used in this program
import javax.swing.Box;

// Import statements to support actions and responses
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.CaretEvent;

/**
 * Demo of event processing in Swing.  This version illustrates the use of a
 * text field, including a listener to track the movement of the cursor
 * within the text field.
 *
 * @author Terran Lane
 * @version 1.0
 */
public class complexDemo {

  public static void main(String[] args) {
    complexDemo bd=new complexDemo();
    bd._init();
  }

  /* ******************** end of public interface ******************** */

  private int _stateVar;
  private String _strData;
  private JLabel _display=null;
  private JLabel _strDisplay=null;

  private complexDemo() {
    _stateVar=0;
    _strData="";
    _buttonProcessor=new _ButtonProcClass();
  }

  private void _init() {
    JFrame jf=new JFrame("CS351 Button Demo App");
    jf.getContentPane().add(_buildDemoGUI());
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jf.pack();
    jf.setVisible(true);
  }

  private JComponent _buildDemoGUI() {
    Box vb=Box.createVerticalBox();
    vb.add(Box.createHorizontalStrut(600));
    vb.add(_display=new JLabel("i=" + _stateVar));
    Box hb1=Box.createHorizontalBox();
    // use struts to add a little spacing around widgets -- prevents them
    // from running right up to the edge of the window or into each other
    hb1.add(Box.createHorizontalStrut(10));
    hb1.add(_buildTextField());
    hb1.add(Box.createHorizontalStrut(10));
    hb1.add(_strDisplay=new JLabel("Current Str Data: \"" + _strData + "\""));
    hb1.add(Box.createHorizontalStrut(10));
    vb.add(hb1);
    Box hb2=Box.createHorizontalBox();
    hb2.add(_buildIncButton());
    hb2.add(_buildDecButton());
    hb2.add(_buildQuitButton());
    vb.add(hb2);
    return vb;
  }

  // String constants identifing button functionality
  private static final String ACT_INC="INC";
  private static final String ACT_DEC="DEC";
  private static final String ACT_QUIT="QUIT";

  // The listener that will process each of the button clicks
  private ActionListener _buttonProcessor;

  private JTextField _buildTextField() {
    // 15 is the _display_width_ of the text field.  It doesn't actually
    // influence how much text the user can type -- just how much of it can
    // be shown at a time.
    JTextField tf=new JTextField(15);
    // Insert an initial string as a prompt for the user, but set it to
    // selected so that, by default, typing will overwrite it.
    tf.setText("Type text here");
    tf.selectAll();
    tf.addActionListener(new _TextProcClass());
    // Also, add a listener for the "caret" -- the location of the point and
    // mark within the text.  This listener tracks when the user moves the
    // cursor within the text field.
    tf.addCaretListener(new _TextCaretProcClass(tf.getCaret().getDot()));
    // Note: We could also set an ActionCommand string for this text field,
    // but since its listener should only receive data from this component,
    // there should never be any confusion as to who generated the action.
    return tf;
  }

  private JButton _buildIncButton() {
    JButton b=new JButton("inc");
    // When the button is clicked, the action generated will have this string
    // as its "action command"; the receiving listener can query the event
    // with event.getActionCommand() and get back this string to identify
    // where the event came from.
    b.setActionCommand(ACT_INC);
    // When the button is clicked, an action event is generated and sent to
    // each listener registered on its listener list (via the listener's
    // actionPerformed() method).  Add a listener for this button that
    // understands its action and handles it appropriately.
    b.addActionListener(_buttonProcessor);
    return b;
  }

  private JButton _buildDecButton() {
    JButton b=new JButton("dec");
    b.setActionCommand(ACT_DEC);
    b.addActionListener(_buttonProcessor);
    return b;
  }

  private JButton _buildQuitButton() {
    JButton b=new JButton("quit");
    b.setActionCommand(ACT_QUIT);
    b.addActionListener(_buttonProcessor);
    return b;
  }

  // This class implements the "ActionListener" protocol for this class.  It
  // contains only a single method -- the actionPerformed() method -- that is
  // invoked whenever the Component for which this listener is assigned is
  // triggered.  Note that this is a _class_, so it must be instantiated (via
  // new) before it can be added as the listener for a component.
  private class _ButtonProcClass implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (e.getActionCommand().equals(ACT_INC)) {
	++_stateVar;
	if ((e.getModifiers() & ActionEvent.SHIFT_MASK)!=0) { ++_stateVar; }
	if ((e.getModifiers() & ActionEvent.CTRL_MASK)!=0) { ++_stateVar; }
	assert _display!=null;
	_display.setText("i=" + _stateVar);
	return;
      }
      if (e.getActionCommand().equals(ACT_DEC)) {
	--_stateVar;
	if ((e.getModifiers() & ActionEvent.SHIFT_MASK)!=0) { --_stateVar; }
	if ((e.getModifiers() & ActionEvent.CTRL_MASK)!=0) { --_stateVar; }
	assert _display!=null;
	_display.setText("i=" + _stateVar);
	return;
      }
      if (e.getActionCommand().equals(ACT_QUIT)) {
	System.exit(0);
      }
      assert false;
    }
  }

  // This class provides the ActionListener used by the text field.  This
  // could be done in the same ActionListener that we use to process buttons,
  // but this provides a slightly cleaner separation of functionality.  OTOH,
  // it also increases the overhead by adding another class and another
  // object.  Small, in this case, but could add up.  YMMV.
  private class _TextProcClass implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      // Note: In order to get and set text on the JTextField widget that
      // invoked this ActionListener, we need to have a handle to it.  One
      // way to do that would be to make a local private copy of that handle
      // and set it in the constructor to _TextProcClass.  Then, when this
      // function runs, we could just use the local stored copy to access the
      // JTextField.  A more general, but very slightly slower, alternative
      // is to use the getSource() method (supported by all Events) to find
      // out what object generated the Event.
      JTextField sourceTF=(JTextField)e.getSource();
      _strData=sourceTF.getText();
      _strDisplay.setText("Current Str Data: \"" + _strData + "\"");
      // Set the state of the TextField to "whole string selected", so that
      // if the user continues typing it overwrites the previous text by
      // default.
      sourceTF.selectAll();
    }
  }

  // This is a listener used for tracking the position of the point (the
  // cursor, or "caret") within the JTextField.
  private class _TextCaretProcClass implements CaretListener {
    public _TextCaretProcClass(int initPos) {
      _last=initPos;
    }

    public void caretUpdate(CaretEvent e) {
      int here=e.getDot();
      if (here<_last) {
	System.out.println("Point moved left; pos=" + here);
      }
      else if (here>_last) {
	System.out.println("Point moved right; pos=" + here);
      }
      else {
	System.out.println("Point unchanged");
      }
      _last=here;
    }

    // track the previous location of the point
    private int _last;
  }
}
