All Downloads are FREE. Search and download functionalities are using the official Maven repository.

editor.ScriptChangeHandler Maven / Gradle / Ivy

package editor;

import editor.undo.AtomicUndoManager;
import editor.undo.StagedStateEdit;

import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.JTextComponent;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.StateEdit;
import java.awt.*;

/**
 */
public class ScriptChangeHandler implements UndoableEditListener, CaretListener
{
  private AtomicUndoManager _undoMgr;
  private JTextComponent _editor;
  private boolean _bDirty;
  private boolean _bPaused;
  private int _iLineNumber;
  private boolean _bLineChanged;
  private int _iBefore;
  private int _iAfter;
  private boolean _bCaretPosChangeIsEdit;
  private DocumentEvent.EventType _eventType;

  public ScriptChangeHandler( AtomicUndoManager undoMgr )
  {
    _undoMgr = undoMgr;
    _bDirty = false;
  }

  public void establishUndoableEditListener( GosuEditor editor )
  {
    editor.setUndoableEditListener( this );
    editor.getEditor().addCaretListener( this );
    _editor = editor.getEditor();
  }

  public void establishUndoableEditListener( JTextComponent editor )
  {
    editor.getDocument().addUndoableEditListener( this );
    editor.addCaretListener( this );
    _editor = editor;
  }

  @Override
  public void undoableEditHappened( UndoableEditEvent e )
  {
    addGosuEditorUndoItem( e );
  }

  @Override
  public void caretUpdate( CaretEvent e )
  {
    if( isPaused() )
    {
      return;
    }

    updatePositionInfo();

    _bCaretPosChangeIsEdit = false;

    final CompoundEdit undoAtom = _undoMgr.getUndoAtom();
    if( undoAtom != null && undoAtom.getPresentationName().equals( "Script Change" ) )
    {
      // Note we invokeLater because undoableEditHappened is called directly after
      // caretUpdate() i.e., it has to set _bCaretPosChangeIsEdit to true in order for us to
      // know whether or not an actual undoable edit is related to the caret pos change.

      EventQueue.invokeLater(
        new Runnable()
        {
          @Override
          public void run()
          {
            if( !_bCaretPosChangeIsEdit )
            {
              if( undoAtom == _undoMgr.getUndoAtom() )
              {
                // The caret moved without a text change e.g., arrow key pressed,
                // so terminate the undo batch with the position change.
                _undoMgr.endUndoAtom();
              }
            }
          }
        } );
    }
  }

  void updatePositionInfo()
  {
    _iBefore = _iAfter;
    _iAfter = _editor.getCaretPosition();
    int iCurrentLine = _editor.getDocument().getDefaultRootElement().getElementIndex( _iAfter );
    _bLineChanged = iCurrentLine != _iLineNumber;
    _iLineNumber = iCurrentLine;
  }

  public int getBefore()
  {
    return _iBefore;
  }

  public int getAfter()
  {
    return _iAfter;
  }

  protected void setDirty( boolean bDirty )
  {
    _bDirty = bDirty;
  }

  public boolean isDirty()
  {
    return _bDirty;
  }

  protected void setPaused( boolean bPaused )
  {
    _bPaused = bPaused;
  }

  public boolean isPaused()
  {
    return _bPaused;
  }

  public JTextComponent getEditor()
  {
    return _editor;
  }

  protected void addGosuEditorUndoItem( UndoableEditEvent e )
  {
    if( isPaused() )
    {
      return;
    }

    // Set _bCaretPosChangeIsEdit so caretUpdate() doesn't terminate the undo batch/atom
    _bCaretPosChangeIsEdit = true;

    CompoundEdit undoAtom = _undoMgr.getUndoAtom();

    if( e.getEdit() instanceof DocumentEvent )
    {
      DocumentEvent docEvent = (DocumentEvent)e.getEdit();
      try
      {
        if( docEvent.getLength() > 1 ||
            docEvent.getType() != _eventType ||
            _bLineChanged )
        {
          // If the undo event is a multi-character change e.g., something pasted in, we
          // want to treat it as a single undo item separate from a Script Change atom.
          // So we must end the current Script Change batch (if exists) before we add this
          // undo event.

          // Ditto if the event type changed e.g., changed from inserting text to deleting text.
          // So if you start backspacing in the editor you essentially get a new undo item for the
          // group of characters you backspace over until you start typing again.

          undoAtom = _undoMgr.getUndoAtom();
          if( undoAtom != null && undoAtom.getPresentationName().equals( "Script Change" ) )
          {
            _undoMgr.endUndoAtom();
            undoAtom = null; // to start a new undo atom for when we change event types
          }

          if( docEvent.getLength() > 1 )
          {
            // We always put a multi-character change in a single batch
            addDocEditChange( e );
            return;
          }
        }
      }
      finally
      {
        _eventType = docEvent.getType();
      }
    }

    if( undoAtom == null || !undoAtom.getPresentationName().equals( "Script Change" ) )
    {
      // There is no Script Change undo atom. Add a new one to collect subsequent text changes

      _undoMgr.beginUndoAtom( "Script Change" );

      final CompoundEdit newUndoAdtom = _undoMgr.getUndoAtom();
      _undoMgr.addChangeListener(
        new ChangeListener()
        {
          @Override
          public void stateChanged( ChangeEvent e )
          {
            if( isPaused() )
            {
              return;
            }
            if( _undoMgr.getUndoAtom() == newUndoAdtom )
            {
              // End the Script Change undo atom/batch.
              // Since this ScriptChangeHandler isPaused() while script change undo items are added
              // to the undo mgr, we assume some other undo change happened which should terminate
              // the script change
              _undoMgr.removeChangeListener( this );
              _undoMgr.endUndoAtom();
            }
            else if( !newUndoAdtom.isInProgress() )
            {
              _undoMgr.removeChangeListener( this );
            }
          }
        } );
    }

    addDocEditChange( e );
  }

  private void addDocEditChange( UndoableEditEvent e )
  {
    ScriptEditorUndoItem undoItem = new ScriptEditorUndoItem( this, e.getEdit() );
    addUndoItem( new StagedStateEdit( undoItem, "Script Change" ) );
  }

  private void addUndoItem( StateEdit transaction )
  {
    setDirty( true );
    transaction.end();

    setPaused( true );
    try
    {
      _undoMgr.addEdit( transaction );
    }
    finally
    {
      setPaused( false );
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy