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

editor.search.AbstractSearchDialog Maven / Gradle / Ivy

There is a newer version: 1.18.1
Show newest version
package editor.search;

import editor.EditorHost;
import editor.FileTree;
import editor.FileTreeUtil;
import editor.GosuPanel;
import editor.LabFrame;
import editor.NodeKind;
import editor.Scheme;
import editor.util.AbstractDialog;
import editor.util.DirectoryEditor;
import editor.util.EditorUtilities;
import editor.util.LabButton;
import editor.util.ModalEventQueue;
import java.nio.file.Path;
import gw.util.PathUtil;
import editor.util.ProgressFeedback;
import gw.lang.reflect.json.IJsonIO;

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;

/**
 */
public abstract class AbstractSearchDialog extends AbstractDialog
{
  private final FileTree _searchDir;
  private final boolean _bReplace;
  private JComboBox _cbSearch;
  private JComboBox _cbReplace;
  private JCheckBox _checkCase;
  private JCheckBox _checkWords;
  private JCheckBox _checkRegex;
  private JRadioButton _rbProject;
  private JRadioButton _rbDirectory;
  private DirectoryEditor _cbDir;
  private JRadioButton _rbScope;
  private JComboBox _cbScope;
  private JCheckBox _checkFileMask;
  private JComboBox _cbFileMasks;
  private DialogStateHandler _stateHandler;
  private List _prevSearchTree;
  private JPanel _separator2;
  private JPanel _separator3;

  public AbstractSearchDialog( FileTree searchDir, boolean bReplace, String title )
  {
    super( LabFrame.instance(), title, false );
    _searchDir = searchDir;
    _bReplace = bReplace;
    configUi();
  }

  protected abstract State getState();
  protected abstract void setState( State state );

  protected boolean isReplace()
  {
    return _bReplace;
  }

  protected void setScope( SearchScope scope )
  {
    _rbScope.setSelected( true );
    _cbScope.setSelectedItem( scope );
  }

  protected void setLocal()
  {
    setScope( SearchScope.CurrentFile );
    EventQueue.invokeLater( () -> setScope( SearchScope.CurrentFile ) );

    _rbProject.setVisible( false );
    _rbDirectory.setVisible( false );
    _cbDir.setVisible( false );
    _rbScope.setVisible( false );
    _cbScope.setVisible( false );
    _separator2.setVisible( false );
    _separator3.setVisible( false );
    _checkFileMask.setVisible( false );
    _cbFileMasks.setVisible( false );
  }

  private void configUi()
  {
    JComponent contentPane = (JComponent)getContentPane();
    contentPane.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
    contentPane.setLayout( new BorderLayout() );

    _stateHandler = new DialogStateHandler();
    
    JPanel mainPanel = new JPanel( new BorderLayout() );
    mainPanel.setBorder( BorderFactory.createLineBorder( Scheme.active().getScrollbarBorderColor() ) );
    mainPanel.add( makeSearchPanel(), BorderLayout.CENTER );

    contentPane.add( mainPanel, BorderLayout.CENTER );

    JPanel south = new JPanel( new BorderLayout() );
    south.setBackground( Scheme.active().getMenu() );
    south.setBorder( BorderFactory.createEmptyBorder( 4, 0, 0, 0 ) );
    JPanel filler = new JPanel();
    filler.setBackground( Scheme.active().getMenu() );
    south.add( filler, BorderLayout.CENTER );

    JPanel buttonPanel = new JPanel();
    buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.X_AXIS ) );
    buttonPanel.setBackground( Scheme.active().getMenu() );

    JButton btnFind = new LabButton( new FindActionHandler() );
    btnFind.setMnemonic( 'F' );
    buttonPanel.add( btnFind );
    getRootPane().setDefaultButton( btnFind );

    JButton btnCancel = new LabButton( "Cancel" );
    btnCancel.addActionListener( e -> close() );
    buttonPanel.add( btnCancel );

    south.add( buttonPanel, BorderLayout.EAST );
    contentPane.add( south, BorderLayout.SOUTH );
    contentPane.setBackground( Scheme.active().getMenu() );

    mapCancelKeystroke( "Cancel", this::close );

    setSize( 400, _bReplace ? 420 : 400 );

    EditorUtilities.centerWindowInFrame( this, getOwner() );

    EventQueue.invokeLater(
      () -> {
        applyState();
        if( _searchDir != FileTreeUtil.getRoot() )
        {
          _rbDirectory.setSelected( true );
          _cbDir.setText( PathUtil.getAbsolutePathName( _searchDir.getFileOrDir() ) );
        }
        else if( !_rbScope.isSelected() )
        {
          _rbProject.setSelected( true );
        }
        _stateHandler.actionPerformed( null );
        _cbSearch.requestFocus();

        setTextFromEditor();
      } );

  }

  private void setTextFromEditor()
  {
    EditorHost editor = LabFrame.instance().getGosuPanel().getCurrentEditor();
    if( editor != null )
    {
      String selection = editor.getEditor().getSelectedText();
      if( selection != null )
      {
        _cbSearch.setSelectedItem( selection );
        JTextComponent textField = (JTextComponent)_cbSearch.getEditor().getEditorComponent();
        textField.setText( selection );
        textField.select( 0, selection.length() );
      }
    }
  }

  private void applyState()
  {
    if( getState() != null )
    {
      getState().restore( this );
    }
  }

  protected String getPattern()
  {
    return (String)_cbSearch.getSelectedItem();
  }

  protected boolean isCaseSensitive()
  {
    return _checkCase.isSelected();
  }

  protected boolean isWholeWords()
  {
    return _checkWords.isSelected();
  }

  protected boolean isRegex()
  {
    return _checkRegex.isSelected();
  }

  protected void find()
  {
    setState( new State().save( this ) );

    close();

    _prevSearchTree = getSearchResults();

    GosuPanel gosuPanel = LabFrame.instance().getGosuPanel();
    gosuPanel.showSearches( false );
    SearchPanel searchPanel = gosuPanel.showSearches( true );
    searchPanel.showReplace( _bReplace );
    searchPanel.setReplacePattern( (String)_cbReplace.getSelectedItem() );

    boolean[] bFinished = {false};
    ProgressFeedback.runWithProgress( "Searching...",
                                      progress -> EventQueue.invokeLater( () -> {
                                        progress.setLength( numOfFiles() );

                                        addReplaceInfo( searchPanel );

                                        String text = (String)_cbSearch.getSelectedItem();
                                        SearchTree results = new SearchTree( "$count occurrences of '" + text + "' in " + getScopeName(), NodeKind.Directory, SearchTree.empty() );
                                        searchPanel.add( results );

                                        TextSearcher searcher = new TextSearcher( text, _checkCase.isSelected(), _checkWords.isSelected(), _checkRegex.isSelected() );
                                        searcher.searchTrees( getSelectedTrees(), results, ft -> include( ft, getFileMatchRegex() ), progress );
                                        selectFirstMatch( results );
                                        bFinished[0] = true;
                                      } ) );
    new ModalEventQueue( () -> !bFinished[0] ).run();
  }

  private int numOfFiles()
  {
    int count = 0;
    for( FileTree ft: getSelectedTrees() )
    {
      count += ft.getTotalFiles();
    }
    return count;
  }

  private List getSelectedTrees()
  {
    if( _rbScope.isSelected() )
    {
      switch( (SearchScope)_cbScope.getSelectedItem() )
      {
        case CurrentFile:
          return Collections.singletonList( FileTreeUtil.getRoot().find( LabFrame.instance().getGosuPanel().getCurrentFile() ) );
        case OpenFiles:
          return LabFrame.instance().getGosuPanel().getOpenFilesInProject();
        case SelectedFiles:
          return Collections.singletonList( LabFrame.instance().getGosuPanel().getExperimentView().getSelectedTree() );
        case PreviousSearchFiles:
          return _prevSearchTree;
        default:
          throw new IllegalStateException();
      }
    }
    else
    {
      return Collections.singletonList( getSearchDir() );
    }
  }

  private List getSearchResults()
  {
    SearchPanel searchPanel = LabFrame.instance().getGosuPanel().getSearchPanel();
    if( searchPanel == null )
    {
      return Collections.emptyList();
    }

    SearchTree root = (SearchTree)searchPanel.getTree().getModel().getRoot();
    ArrayList result = new ArrayList<>();
    findFilesFromPreviousSearch( root, result );
    return result;
  }

  private void findFilesFromPreviousSearch( SearchTree root, List result )
  {
    SearchTree.SearchTreeNode node = root.getNode();
    if( node != null && node.getFile() != null && node.getLocation() == null )
    {
      result.add( node.getFile() );
    }
    else
    {
      for( int i = 0; i < root.getChildCount(); i++ )
      {
        SearchTree child = root.getChildAt( i );
        findFilesFromPreviousSearch( child, result );
      }
    }
  }

  private void addReplaceInfo( SearchPanel searchPanel )
  {
    if( _bReplace )
    {
      String text = (String)_cbReplace.getSelectedItem();
      SearchTree results = new SearchTree( "Replace occurrences with '" + text + "'", NodeKind.Info, SearchTree.empty() );
      searchPanel.add( results );
    }
  }

  private void selectFirstMatch( SearchTree results )
  {
    if( results.getChildCount() == 0 )
    {
      results.select();
    }
    else
    {
      selectFirstMatch( results.getChildAt( 0 ) );
    }
  }

  private List getFileMatchRegex()
  {
    if( _checkFileMask.isSelected() )
    {
      String mask = (String)_cbFileMasks.getSelectedItem();
      if( mask != null && mask.isEmpty() )
      {
        List list = new ArrayList<>();
        for( StringTokenizer tok = new StringTokenizer( mask, ";" ); tok.hasMoreTokens(); )
        {
          String ext = tok.nextToken().trim();
          list.add( StringUtil.wildcardToRegex( ext ) );
        }
        return list;
      }
    }
    return Collections.emptyList();
  }

  private boolean include( FileTree ft, List fileMatchRegex )
  {
    if( !fileMatchRegex.isEmpty() )
    {
      for( String regex : fileMatchRegex )
      {
        if( ft.getName().toLowerCase().matches( regex ) )
        {
          return true;
        }
      }
      return false;
    }
    return true;
  }

  private String getScopeName()
  {
    if( _rbProject.isSelected() )
    {
      return "Experiment";
    }
    if( _rbDirectory.isSelected() )
    {
      return _cbDir.getText();
    }
    if( _rbScope.isSelected() )
    {
      return ((SearchScope)_cbScope.getSelectedItem()).getLabel();
    }

    throw new IllegalStateException();
  }

  private JComponent makeSearchPanel()
  {
    JPanel configPanel = new JPanel( new GridBagLayout() );
    configPanel.setBorder( BorderFactory.createEmptyBorder( 10, 10, 10, 10 ) );

    final GridBagConstraints c = new GridBagConstraints();

    int iY = 0;

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY;
    c.gridwidth = 1;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 0, 0, 0, 5 );
    JLabel label = new JLabel( "Text to find:" );
    configPanel.add( label, c );

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridx = 1;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.insets = new Insets( 0, 0, 5, 0 );
    _cbSearch = new JComboBox<>();
    _cbSearch.setEditable( true );
    configPanel.add( _cbSearch, c );


    _cbReplace = new JComboBox<>();
    _cbReplace.setEditable( true );
    if( _bReplace )
    {
      c.anchor = GridBagConstraints.WEST;
      c.fill = GridBagConstraints.NONE;
      c.gridx = 0;
      c.gridy = iY;
      c.gridwidth = 1;
      c.gridheight = 1;
      c.weightx = 0;
      c.weighty = 0;
      c.insets = new Insets( 0, 0, 0, 5 );
      label = new JLabel( "Replace with:" );
      configPanel.add( label, c );

      c.anchor = GridBagConstraints.WEST;
      c.fill = GridBagConstraints.HORIZONTAL;
      c.gridx = 1;
      c.gridy = iY++;
      c.gridwidth = GridBagConstraints.REMAINDER;
      c.gridheight = 1;
      c.weightx = 1;
      c.weighty = 0;
      c.insets = new Insets( 0, 0, 0, 0 );
      configPanel.add( _cbReplace, c );
    }


    //---------------------------------------------------------------------------------------
    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridx = 0;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 5, 0 );
    JPanel separator1 = new JPanel();
    separator1.setBorder( BorderFactory.createMatteBorder( 1, 0, 0, 0, Scheme.active().getControlShadow() ) );
    configPanel.add( separator1, c );


    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 0, 0, 0, 0 );
    _checkCase = new JCheckBox( "Case sensitive" );
    _checkCase.setMnemonic( 'C' );
    configPanel.add( _checkCase, c );

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 0, 0 );
    _checkWords = new JCheckBox( "Whole words only" );
    _checkWords.setMnemonic( 'R' );
    configPanel.add( _checkWords, c );

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 0, 0 );
    _checkRegex = new JCheckBox( "Regular expression" );
    _checkRegex.setMnemonic( 'G' );
    _checkRegex.addActionListener( _stateHandler );
    configPanel.add( _checkRegex, c );

    //-----------------------------------------------------------------------------------
    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridx = 0;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 5, 0 );
    _separator2 = new JPanel();
    _separator2.setBorder( BorderFactory.createMatteBorder( 1, 0, 0, 0, Scheme.active().getControlShadow() ) );
    configPanel.add( _separator2, c );


    ButtonGroup group = new ButtonGroup();

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 0, 0, 0, 0 );
    _rbProject = new JRadioButton( "Whole experiment" );
    _rbProject.setMnemonic( 'H' );
    _rbProject.addActionListener( _stateHandler );
    group.add( _rbProject );
    configPanel.add( _rbProject, c );


    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY;
    c.gridwidth = 1;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 0, 0 );
    _rbDirectory = new JRadioButton( "Directory:" );
    _rbDirectory.setMnemonic( 'D' );
    _rbDirectory.addActionListener( _stateHandler );
    group.add( _rbDirectory );
    configPanel.add( _rbDirectory, c );

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridx = 1;
    c.gridy = iY++;
    c.gridwidth = 1;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 0, 0 );
    _cbDir = new DirectoryEditor( "Directory", PathUtil.getAbsolutePathName( _searchDir.getFileOrDir() ), LabFrame::instance );
    configPanel.add( _cbDir, c );


    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY;
    c.gridwidth = 1;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 0, 0 );
    _rbScope = new JRadioButton( "Scope:" );
    _rbScope.setMnemonic( 'S' );
    _rbScope.addActionListener( _stateHandler );
    group.add( _rbScope);
    configPanel.add( _rbScope, c );

    _rbProject.setSelected( true );

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridx = 1;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 0, 0 );
    _cbScope = new JComboBox<>( SearchScope.values() );
    configPanel.add( _cbScope, c );

    //-----------------------------------------------------------------------------------
    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridx = 0;
    c.gridy = iY++;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 5, 0 );
    _separator3 = new JPanel();
    _separator3.setBorder( BorderFactory.createMatteBorder( 1, 0, 0, 0, Scheme.active().getControlShadow() ) );
    configPanel.add( _separator3, c );

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.NONE;
    c.gridx = 0;
    c.gridy = iY;
    c.gridwidth = 1;
    c.gridheight = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets( 0, 0, 0, 0 );
    _checkFileMask = new JCheckBox( "Path mask(s):" );
    _checkFileMask.setMnemonic( 'M' );
    _checkFileMask.addActionListener( _stateHandler );
    configPanel.add( _checkFileMask, c );

    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridx = 1;
    c.gridy = iY;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.insets = new Insets( 5, 0, 0, 0 );
    _cbFileMasks= new JComboBox<>();
    _cbFileMasks.setEditable( true );
    configPanel.add( _cbFileMasks, c );

    return configPanel;
  }

  public FileTree getSearchDir()
  {
    if( _rbDirectory.isSelected() )
    {
      Path file = PathUtil.create( _cbDir.getText() );
      if( PathUtil.exists( file ) )
      {
        FileTree fileTree = FileTreeUtil.getRoot().find( file );
        if( fileTree != null )
        {
          return fileTree;
        }
      }
    }
    return FileTreeUtil.getRoot();
  }

  private class DialogStateHandler implements ActionListener
  {
    @Override
    public void actionPerformed( ActionEvent e )
    {
      _checkWords.setEnabled( !_checkRegex.isSelected() );
      _cbDir.setEnabled( _rbDirectory.isSelected() );
      _cbScope.setEnabled( _rbScope.isSelected() );
      _cbDir.setEnabled( _rbDirectory.isSelected() );
      _cbFileMasks.setEnabled( _checkFileMask.isSelected() );
    }
  }

  public static class State implements IJsonIO
  {
    String[] _searchHistory;
    String[] _replaceHistory;

    boolean _case;
    boolean _words;
    boolean _regex;

    boolean _project;
    boolean _dir;
    boolean _scope;

    String _selectedDir;

    SearchScope _selectedScope;

    boolean _mask;
    String _selectedMask;
    String[] _masks;

    public State()
    {
    }

    public State save( AbstractSearchDialog dlg )
    {
      _searchHistory = makeArray( dlg._cbSearch );
      _replaceHistory = makeArray(dlg. _cbReplace );

      _case = dlg._checkCase.isSelected();
      _words = dlg._checkWords.isSelected();
      _regex = dlg._checkRegex.isSelected();

      _project = dlg._rbProject.isSelected();
      _dir = dlg._rbDirectory.isSelected();
      _scope = dlg._rbScope.isSelected();
      _selectedScope = (SearchScope)dlg._cbScope.getSelectedItem();
      _selectedDir = dlg._cbDir.getText();

      _mask = dlg._checkFileMask.isSelected();
      _selectedMask = (String)dlg._cbFileMasks.getSelectedItem();
      _masks = makeArray( dlg._cbFileMasks );

      return this;
    }

    public void restore( AbstractSearchDialog dlg )
    {
      dlg._cbSearch.setModel( new DefaultComboBoxModel<>( _searchHistory ) );
      dlg._cbSearch.getEditor().setItem( null );
      dlg._cbReplace.setModel( new DefaultComboBoxModel<>( _replaceHistory ) );
      dlg._cbReplace.getEditor().setItem( null );

      dlg._checkCase.setSelected( _case );
      dlg._checkWords.setSelected( _words );
      dlg._checkRegex.setSelected( _regex );

      dlg._rbProject.setSelected( _project );
      dlg._rbDirectory.setSelected( _dir );
      dlg._rbScope.setSelected( _scope );

      dlg._cbDir.setText( _selectedDir );

      dlg._cbScope.setSelectedItem( _selectedScope );

      dlg._checkFileMask.setSelected( _mask );
      dlg._cbFileMasks.setSelectedItem( _selectedMask );
      dlg._cbFileMasks.setModel( new DefaultComboBoxModel<>( _masks ) );
    }

    private String[] makeArray( JComboBox cb )
    {
      DefaultComboBoxModel model = (DefaultComboBoxModel)cb.getModel();
      int size = Math.min( 20, model.getSize() );
      String selectedItem = (String)cb.getSelectedItem();
      int extra = 0;
      if( selectedItem != null && !selectedItem.isEmpty() && model.getIndexOf( selectedItem ) < 0  )
      {
        extra = 1;
      }
      String[] array = new String[size+extra];
      if( extra > 0 )
      {
        array[0] = selectedItem;
      }
      for( int i = 0; i < size; i++ )
      {
        array[i+extra] = model.getElementAt( i );
      }
      return array;
    }
  }

  private class FindActionHandler extends AbstractAction
  {
    private FindActionHandler()
    {
      super( _bReplace ? "Replace" : "Find" );
    }

    @Override
    public boolean isEnabled()
    {
      String pattern = ((JTextComponent)_cbSearch.getEditor().getEditorComponent()).getText();
      return pattern != null && !pattern.isEmpty();
    }

    @Override
    public void actionPerformed( ActionEvent e )
    {
      find();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy