/* ViewMenu.java
 *
 * created: Tue Dec 29 1998
 *
 * This file is part of Artemis
 *
 * Copyright (C) 1998,1999,2000  Genome Research Limited
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /nfs/disk222/yeastpub/Repository/powmap/diana/components/ViewMenu.java,v 1.58 2000/11/01 15:01:53 kmr Exp $
 */

package diana.components;

import diana.*;
import diana.sequence.*;
import diana.plot.*;

import uk.ac.sanger.pathogens.*;
import uk.ac.sanger.pathogens.embl.InvalidRelationException;
import uk.ac.sanger.pathogens.embl.Key;
import uk.ac.sanger.pathogens.embl.InvalidKeyException;
import uk.ac.sanger.pathogens.embl.Range;

import java.io.*;
import java.awt.*;
import java.awt.event.*;

/**
 *  A popup menu with viewing commands.
 *
 *  @author Kim Rutherford
 *  @version $Id: ViewMenu.java,v 1.58 2000/11/01 15:01:53 kmr Exp $
 **/

public class ViewMenu extends SelectionMenu {
  /**
   *  Create a new ViewMenu object.
   *  @param frame The Frame that owns this Menu.
   *  @param selection The Selection that the commands in the menu will
   *    operate on.
   *  @param entry_group The EntryGroup object where new features/entries will
   *    be added.
   *  @param goto_event_source The object the we will call makeBaseVisible ()
   *    on.
   **/
  public ViewMenu (final Frame frame,
                   final Selection selection,
                   final GotoEventSource goto_event_source,
                   final EntryGroup entry_group) {
    super (frame, "View", selection);
    this.entry_group = entry_group;
    this.selection = selection;
    this.goto_event_source = goto_event_source;

    plot_features_item =
      new MenuItem ("Show Feature Plots",
                    new MenuShortcut (PLOT_FEATURES_KEY));
    plot_features_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        plotSelectedFeatures (getParentFrame (), getSelection ());
        System.gc ();
      }
    });

    view_feature_item = new MenuItem ("View Selected Features",
                                      new MenuShortcut (VIEW_FEATURES_KEY));
    view_feature_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        viewSelectedFeatures (getParentFrame (), getSelection ());
      }
    });

    view_selection_item = new MenuItem ("View Selection");
    view_selection_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        new SelectionViewer (getSelection (), entry_group);
      }
    });

    feature_info_item = new MenuItem ("Show Feature Statistics");
    feature_info_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        viewSelectedFeatureInfo ();
      }
    });

    view_feature_bases_item = new MenuItem ("View Feature Bases");
    view_feature_bases_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        viewSelectedFeatureBases (true);
      }
    });

    view_feature_bases_as_fasta_item =
      new MenuItem ("View Feature Bases As FASTA");
    view_feature_bases_as_fasta_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        viewSelectedFeatureBases (false);
      }
    });

    view_feature_aa_item = new MenuItem ("View Feature Amino Acids");
    view_feature_aa_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        viewSelectedFeatureAminoAcids (true);
      }
    });

    view_feature_aa_as_fasta_item =
      new MenuItem ("View Feature Amino Acids As FASTA");
    view_feature_aa_as_fasta_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        viewSelectedFeatureAminoAcids (false);
      }
    });

    overview_item = new MenuItem ("Show Overview",
                                  new MenuShortcut (OVERVIEW_KEY));
    overview_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        new EntryGroupInfoDisplay (getParentFrame (), entry_group);
      }
    });

    forward_overview_item = new MenuItem ("Show Forward Strand Overview");
    forward_overview_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        new EntryGroupInfoDisplay (getParentFrame (), entry_group,
                                   EntryGroupInfoDisplay.FORWARD);
      }
    });

    reverse_overview_item = new MenuItem ("Show Reverse Strand Overview");
    reverse_overview_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        new EntryGroupInfoDisplay (getParentFrame (), entry_group,
                                   EntryGroupInfoDisplay.REVERSE);
      }
    });

    view_cds_item = new MenuItem ("Show CDS Genes And Products");
    view_cds_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        final FeaturePredicate feature_predicate =
          new FeatureKeyPredicate (Key.CDS);

        final FilteredEntryGroup filtered_entry_group =
          new FilteredEntryGroup (entry_group, feature_predicate);

        final FeatureListFrame feature_list_frame =
          new FeatureListFrame ("CDS /genes and /product" +
                                " (filtered from: " +
                                getParentFrame ().getTitle () + ")",
                                selection, goto_event_source,
                                filtered_entry_group);

        feature_list_frame.getFeatureList ().setShowGenes (true);
        feature_list_frame.getFeatureList ().setShowProducts (true);

        feature_list_frame.setVisible (true);
      }
    });

    Menu search_results_menu = null;

    search_results_menu = new Menu ("Search Results");

    final boolean sanger_options =
      Options.getOptions ().getPropertyTruthValue ("sanger_options");

    final ExternalProgramVector external_programs =
      Options.getOptions ().getExternalPrograms ();

    for (int i = 0 ; i < external_programs.size () ; ++i) {
      final ExternalProgram external_program =
        external_programs.elementAt (i);

      final MenuItem new_menu =
        makeSearchResultsMenu (external_program, false, sanger_options);
      search_results_menu.add (new_menu);
    }

    if (sanger_options) {
      search_results_menu.addSeparator ();

      for (int i = 0 ; i < external_programs.size () ; ++i) {
        final ExternalProgram external_program =
          external_programs.elementAt (i);

        final MenuItem new_menu =
          makeSearchResultsMenu (external_program, true, sanger_options);
        search_results_menu.add (new_menu);
      }
    }

    final int MAX_FILTER_FEATURE_COUNT = 10000;

    final Menu feature_filters_menu = new Menu ("Feature Filters");

    final MenuItem bad_start_codons_item =
      new MenuItem ("Suspicious Start Codons ...");
    bad_start_codons_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) {
          showBadStartCodons (getParentFrame (),
                              selection, entry_group, goto_event_source);
        }
      }
    });

    final MenuItem bad_stop_codons_item =
      new MenuItem ("Suspicious Stop Codons ...");
    bad_stop_codons_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) {
          showBadStopCodons (getParentFrame (),
                             selection, entry_group, goto_event_source);
        }
      }
    });

    final MenuItem bad_feature_keys_item =
      new MenuItem ("Non EMBL Keys ...");
    bad_feature_keys_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) {
          showNonEMBLKeys (getParentFrame (),
                           selection, entry_group, goto_event_source);
        }
      }
    });

    final MenuItem duplicated_keys_item =
      new MenuItem ("Duplicated Features ...");
    duplicated_keys_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) {
          showDuplicatedFeatures (getParentFrame (),
                                  selection, entry_group, goto_event_source);
        }
      }
    });

    final MenuItem overlapping_cds_features_item =
      new MenuItem ("Overlapping CDS Features ...");
    overlapping_cds_features_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) {
          showOverlappingCDSs (getParentFrame (),
                               selection, entry_group, goto_event_source);
        }
      }
    });

    final MenuItem missing_qualifier_features_item =
      new MenuItem ("Features Missing Required Qualifiers ...");
    missing_qualifier_features_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) {
          showMissingQualifierFeatures (getParentFrame (),
                                        selection,
                                        entry_group, goto_event_source);
        }
      }
    });

    final MenuItem filter_by_key_item =
      new MenuItem ("Filter By Key ...");
    filter_by_key_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) {
          showFilterByKey (getParentFrame (),
                           selection, entry_group, goto_event_source);
        }
      }
    });

    final MenuItem filter_by_selection_item =
      new MenuItem ("Selected Features ...");
    filter_by_selection_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        if (!checkForSelectionFeatures (MAXIMUM_SELECTED_FEATURES,
                                        "really view all (>" +
                                        MAXIMUM_SELECTED_FEATURES +
                                        ") selected features?")) {
          return;
        }

        showFilterBySelection (getParentFrame (),
                               selection, entry_group, goto_event_source);
      }
    });

    feature_filters_menu.add (bad_start_codons_item);
    feature_filters_menu.add (bad_stop_codons_item);
    feature_filters_menu.add (bad_feature_keys_item);
    feature_filters_menu.add (duplicated_keys_item);
    feature_filters_menu.add (overlapping_cds_features_item);
    feature_filters_menu.add (missing_qualifier_features_item);
    feature_filters_menu.addSeparator ();
    feature_filters_menu.add (filter_by_key_item);
    feature_filters_menu.add (filter_by_selection_item);

    add (view_feature_item);
    add (view_selection_item);
    addSeparator ();
    if (search_results_menu != null) {
      add (search_results_menu);
    }
    add (view_cds_item);
    add (feature_filters_menu);
    addSeparator ();
    add (overview_item);
    add (forward_overview_item);
    add (reverse_overview_item);
    addSeparator ();
    add (view_feature_bases_item);
    add (view_feature_bases_as_fasta_item);
    add (view_feature_aa_item);
    add (view_feature_aa_as_fasta_item);
    addSeparator ();
    add (feature_info_item);
    add (plot_features_item);
  }

  /**
   *  The shortcut for Show Feature Plots.
   **/
  final static int PLOT_FEATURES_KEY = KeyEvent.VK_W;

  /**
   *  The shortcut for View Selected Features.
   **/
  final static int VIEW_FEATURES_KEY = KeyEvent.VK_V;

  /**
   *  The shortcut for Show Overview.
   **/
  final static int OVERVIEW_KEY = KeyEvent.VK_O;

  /**
   *  The shortcut for View FASTA in browser.
   **/
  final static int FASTA_IN_BROWSER_KEY = KeyEvent.VK_F;

  /**
   *  The shortcut for View FASTA.
   **/
  final static int VIEW_FASTA_KEY = KeyEvent.VK_R;

  /**
   *  The shortcut for View BLASTP in browser.
   **/
  final static int BLASTP_IN_BROWSER_KEY = KeyEvent.VK_B;

  /**
   *  The shortcut for View BLASTP.
   **/
  final static int VIEW_BLASTP_KEY = KeyEvent.VK_TAB;

  /**
   *  The shortcut for View HTH.
   **/
  final static int VIEW_HTH_KEY = KeyEvent.VK_H;

  /**
   *  Make a MenuItem for viewing the results of running the given program.
   *  @param send_to_browser if true the results should be sent straight to
   *    the web browser rather than using a SearchResultViewer object.
   *  @param sanger_options true if the sanger_options is set to true in the
   *    options file.
   **/
  private MenuItem makeSearchResultsMenu (final ExternalProgram program,
                                          final boolean send_to_browser,
                                          final boolean sanger_options) {
    final String program_name = program.getName ();

    final String new_menu_name;

    if (send_to_browser) {
      new_menu_name = program_name + " results (in browser)";
    } else {
      new_menu_name = program_name + " results";
    }

    final MenuItem new_menu;

    if ((sanger_options && send_to_browser || !sanger_options)
        && program_name.equals ("fasta")) {
      new_menu = new MenuItem (new_menu_name,
                               new MenuShortcut (FASTA_IN_BROWSER_KEY));
    } else {
      if ((sanger_options && send_to_browser || !sanger_options)
          && program_name.equals ("blastp")) {
        new_menu = new MenuItem (new_menu_name,
                                 new MenuShortcut (BLASTP_IN_BROWSER_KEY));
      } else {
        if (program_name.equals ("fasta")) {
          new_menu = new MenuItem (new_menu_name,
                                   new MenuShortcut (VIEW_FASTA_KEY));
        } else {
          if (program_name.equals ("blastp")) {
            new_menu = new MenuItem (new_menu_name,
                                     new MenuShortcut (VIEW_BLASTP_KEY));
          } else {
            if (program_name.equals ("hth")) {
              new_menu = new MenuItem (new_menu_name,
                                       new MenuShortcut (VIEW_HTH_KEY));
            } else {
              new_menu = new MenuItem (new_menu_name);
            }
          }
        }
      }
    }

    new_menu.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        viewExternalResults (getParentFrame (), getSelection (),
                             program_name, send_to_browser);
      }
    });

    return new_menu;
  }

  /**
   *  Set the CodonUsageAlgorithm that will be passed to the FeatureInfo
   *  constructor.
   **/
  public void setCodonUsageAlgorithm (final CodonUsageAlgorithm algorithm) {
    this.codon_usage_algorithm = algorithm;
  }

  /**
   *  Popup a FeatureListFrame containing the non-pseudo CDS features that
   *  have invalid start codons.
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showBadStartCodons (final Frame parent_frame,
                                         final Selection selection,
                                         final EntryGroup entry_group,
                                         final GotoEventSource goto_source) {
    final FeatureKeyQualifierPredicate cds_predicate =
      new FeatureKeyQualifierPredicate (Key.CDS, "pseudo", false);

    final FeaturePredicate feature_predicate =
      new FeaturePredicate () {
        public boolean testPredicate (final Feature feature) {
          if (!cds_predicate.testPredicate (feature)) {
            return false;
          }

          if (feature.hasValidStartCodon ()) {
            return false;
          } else {
            return true;
          }
        }
      };

    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group, feature_predicate);

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("CDS Features With Suspicious Start Codons" +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }

  /**
   *  Popup a FeatureListFrame containing the non-pseudo CDS features that
   *  have invalid stop codons.
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showBadStopCodons (final Frame parent_frame,
                                        final Selection selection,
                                        final EntryGroup entry_group,
                                        final GotoEventSource goto_source) {
    final FeatureKeyQualifierPredicate cds_predicate =
      new FeatureKeyQualifierPredicate (Key.CDS, "pseudo", false);

    final FeaturePredicate feature_predicate =
      new FeaturePredicate () {
        public boolean testPredicate (final Feature feature) {
          if (!cds_predicate.testPredicate (feature)) {
            return false;
          }

          if (feature.hasValidStopCodon ()) {
            return false;
          } else {
            return true;
          }
        }
      };

    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group, feature_predicate);

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("CDS Features With Suspicious Stop Codons" +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }

  /**
   *  Popup a FeatureListFrame containing the features that have non-EMBL keys.
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showNonEMBLKeys (final Frame parent_frame,
                                      final Selection selection,
                                      final EntryGroup entry_group,
                                      final GotoEventSource goto_source) {
    final FeaturePredicate feature_predicate =
      new FeaturePredicate () {
        public boolean testPredicate (final Feature feature) {
          if (feature.hasValidEMBLKey ()) {
            return false;
          } else {
            return true;
          }
        }
      };

    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group, feature_predicate);

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("Features With A Non-EMBL Key" +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }


  /**
   *  Popup a FeatureListFrame containing the features that have the same key
   *  and location as another features (ie. duplicates).
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showDuplicatedFeatures (final Frame parent_frame,
                                             final Selection selection,
                                             final EntryGroup entry_group,
                                             final GotoEventSource goto_source) {
    final FeaturePredicate feature_predicate =
      new FeaturePredicate () {
        public boolean testPredicate (final Feature feature) {
          final Entry feature_entry = feature.getEntry ();

          final int feature_index = feature_entry.indexOf (feature);

          if (feature_index + 1 == feature_entry.getFeatureCount ()) {
            // last in the Entry
            return false;
          }

          final Feature next_feature =
            feature_entry.getFeature (feature_index + 1);

          if (feature.getKey ().equals (next_feature.getKey ()) &&
              feature.getLocation ().equals (next_feature.getLocation ())) {
            return true;
          } else {
            return false;
          }
        }
      };

    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group, feature_predicate);

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("Duplicated Features" +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }

  /**
   *  Popup a FeatureListFrame containing those CDS features that overlap with
   *  the next feature.
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showOverlappingCDSs (final Frame parent_frame,
                                          final Selection selection,
                                          final EntryGroup entry_group,
                                          final GotoEventSource goto_source) {
    final FeatureKeyPredicate cds_predicate =
      new FeatureKeyPredicate (Key.CDS);

    final FeaturePredicate feature_predicate =
      new FeaturePredicate () {
        public boolean testPredicate (final Feature test_feature) {
          if (!cds_predicate.testPredicate (test_feature)) {
            return false;
          }

          final Range feature_range = test_feature.getMaxRawRange ();

          final FeatureVector overlapping_features;

          try {
            overlapping_features =
              entry_group.getFeaturesInRange (feature_range);
          } catch (OutOfRangeException e) {
            throw new Error ("internal error - unexpected exception: " + e);
          }

          for (int i = 0 ; i < overlapping_features.size () ; ++i ) {
            final Feature current_feature = overlapping_features.elementAt (i);

            if (current_feature != test_feature &&
                cds_predicate.testPredicate (current_feature)) {
              return true;
            }
          }

          return false;
        }
      };

    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group, feature_predicate);

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("Overlapping CDS Features" +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }

  /**
   *  Popup a FeatureListFrame containing the features that are missing
   *  required EMBL qualifiers.
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showMissingQualifierFeatures (final Frame parent_frame,
                                                   final Selection selection,
                                                   final EntryGroup
                                                     entry_group,
                                                   final GotoEventSource
                                                     goto_source) {
    final FeaturePredicate feature_predicate =
      new FeaturePredicate () {
        public boolean testPredicate (final Feature feature) {
          if (feature.hasRequiredQualifiers ()) {
            return false;
          } else {
            return true;
          }
        }
      };

    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group, feature_predicate);

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("Features That Are Missing A Required EMBL " +
                            "Qualifier" +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }

  /**
   *  Popup a FeatureListFrame containing only those features that have the
   *  key choosen by the user.
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showFilterByKey (final Frame parent_frame,
                                      final Selection selection,
                                      final EntryGroup entry_group,
                                      final GotoEventSource goto_source) {
    final KeyChooser key_chooser;

    key_chooser = new KeyChooser (Options.getArtemisEntryInformation (),
                                  new Key ("misc_feature"));

    key_chooser.getKeyChoice ().addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent _) {
        showFilterByKeyHelper (parent_frame,
                               key_chooser.getKeyChoice ().getSelectedItem (),
                               selection, entry_group, goto_source);
        key_chooser.setVisible (false);
        key_chooser.dispose ();
      }
    });

    key_chooser.getOKButton ().addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent _) {
        showFilterByKeyHelper (parent_frame,
                               key_chooser.getKeyChoice ().getSelectedItem (),
                               selection, entry_group, goto_source);
        key_chooser.setVisible (false);
        key_chooser.dispose ();
      }
    });

    key_chooser.setVisible (true);
  }

  /**
   *  Popup a FeatureListFrame containing only those features that have the
   *  key choosen by the user.
   *  @param parent_frame The parent Frame.
   *  @param key The key to use in the filter.
   *  @param selection The Selection to pass to the FeatureList.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showFilterByKeyHelper (final Frame parent_frame,
                                            final Key key,
                                            final Selection selection,
                                            final EntryGroup entry_group,
                                            final GotoEventSource
                                              goto_source) {
    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group,
                              new FeatureKeyPredicate (key));

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("Features With This Key: " + key +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }

  /**
   *  Popup a FeatureListFrame containing only those features that are
   *  currently in the Selection.
   *  @param parent_frame The parent Frame.
   *  @param selection The Selection to pass to the FeatureList and to use to
   *    filter the entry_group.
   *  @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
   *  @param goto_source The GotoEventSource to pass to the FeatureList.
   **/
  public static void showFilterBySelection (final Frame parent_frame,
                                            final Selection selection,
                                            final EntryGroup entry_group,
                                            final GotoEventSource
                                              goto_source) {
    final FeaturePredicate predicate =
      new FeatureFromVectorPredicate (selection.getAllFeatures ());

    final FilteredEntryGroup filtered_entry_group =
      new FilteredEntryGroup (entry_group, predicate);

    final FeatureListFrame feature_list_frame =
      new FeatureListFrame ("Features From The Selection: " +
                            " (filtered from: " +
                            parent_frame.getTitle () + ")",
                            selection, goto_source, filtered_entry_group);

    feature_list_frame.setVisible (true);
  }

  /**
   *  If more than this number of features are selected than
   *  viewSelectedFeatures () etc. will ask for confirmation.
   **/
  private static final int MAXIMUM_SELECTED_FEATURES = 10;

  /**
   *  Open a view window for each of the selected features.  The viewer will
   *  listen for feature change events and update itself.
   *  @param frame The Frame to use for MessageDialog components.
   *  @param selection The Selection containing the features to merge.
   **/
  static void viewSelectedFeatures (final Frame frame,
                                    final Selection selection) {
    if (!checkForSelectionFeatures (frame, selection,
                                    MAXIMUM_SELECTED_FEATURES,
                                    "really view all (>" +
                                    MAXIMUM_SELECTED_FEATURES +
                                    ") selected features?")) {
      return;
    }

    final FeatureVector features_to_view = selection.getAllFeatures ();

    for (int i = 0 ; i < features_to_view.size () ; ++i) {
      final Feature selection_feature = features_to_view.elementAt (i);

      new FeatureViewer (selection_feature);
    }
  }

  /**
   *  Open an plot viewing window for each of the selected features.  The plot
   *  viewer will listen for feature change events and update itself.
   *  @param frame The Frame to use for MessageDialog components.
   *  @param selection The Selection containing the features to merge.
   **/
  static void plotSelectedFeatures (final Frame frame,
                                    final Selection selection) {
    if (!checkForSelectionFeatures (frame, selection,
                                    MAXIMUM_SELECTED_FEATURES,
                                    "really plot all (>" +
                                    MAXIMUM_SELECTED_FEATURES +
                                    ") selected features?")) {
      return;
    }

    final FeatureVector features_to_edit = selection.getAllFeatures ();

    for (int i = 0 ; i < features_to_edit.size () ; ++i) {
      final Feature selection_feature = features_to_edit.elementAt (i);

      new FeaturePlotGroup (selection_feature);
    }
  }

  /**
   *  Show the output file from an external program (like fasta) for the
   *  selected Feature objects.  The name of the file to read is stored in a
   *  feature qualifier.  The qualifier used is the program name plus "_file".
   *  @param frame The Frame to use for MessageDialog components.
   *  @param selection The Selection containing the features to merge.
   *  @param send_to_browser if true the results should be sent straight to
   *    the web browser rather than using a SearchResultViewer object.
   **/
  static void viewExternalResults (final Frame frame,
                                   final Selection selection,
                                   final String program_name,
                                   final boolean send_to_browser) {
    if (!checkForSelectionFeatures (frame, selection,
                                    MAXIMUM_SELECTED_FEATURES,
                                    "really view results from " +
                                    "all (>" + MAXIMUM_SELECTED_FEATURES +
                                    ") selected features?")) {
      return;
    }

    final FeatureVector features_to_view = selection.getAllFeatures ();

    for (int i = 0 ; i < features_to_view.size () ; ++i) {
      final Feature this_feature = features_to_view.elementAt (i);

      final String qualifier_value;

      try {
        qualifier_value =
          this_feature.getValueOfQualifier (program_name + "_file");
      } catch (InvalidRelationException e) {
        qualifier_value = null;
      }

      String file_name = qualifier_value;

      if (file_name == null || file_name.length () == 0) {
        new MessageDialog (frame,
                           "Message",
                           "No " + program_name + " results for " +
                           this_feature.getIDString ());

        continue;
      }

      // remove the program name string (if any) from the qualifier, so that
      // we can try to read the file with and without the prefix.
      if (file_name.startsWith (program_name + File.separatorChar)) {
        file_name = file_name.substring (program_name.length () + 1);
      }

      final Document root_document =
        this_feature.getEntry ().getRootDocument ();

      try {
        Document document;

        if (root_document == null) {
          // try the current directory
          document = new FileDocument (new File (file_name));

          if (!document.readable ()) {
            // now try the fasta or blast directory
            document =
              new FileDocument (new File (program_name + File.separatorChar +
                                          file_name));

            if (!document.readable ()) {
              new MessageDialog (frame,
                                 "Message",
                                 "No " + program_name + " results for " +
                                 this_feature.getIDString () +
                                 " (file not found: " + qualifier_value + ")");
              continue;
            }
          }
        } else {
          document = root_document.append (program_name).append (file_name);

          if (!document.readable ()) {
            document = root_document.append (file_name);

            if (!document.readable ()) {
              new MessageDialog (frame,
                                 "Message",
                                 "No " + program_name + " results for " +
                                 this_feature.getIDString () +
                                 " (file not found: " + qualifier_value + ")");

              continue;
            }
          }
        }

        if (send_to_browser) {
          SearchResultViewer.sendToBrowser (document.toString ());
        } else {
          new SearchResultViewer (program_name + " results for " +
                                  this_feature.getIDString () + " from " +
                                  document,
                                  document);
        }
      } catch (ExternalProgramException e) {
        new MessageDialog (frame,
                           "error while open results file: " + e);
      } catch (IOException e) {
        new MessageDialog (frame,
                           "error while open results file: " + e);
      }
    }

//    Diana.getClipboard ().setClip (getSelection ());
  }

  /**
   *  Open a FeatureInfo component for each of the selected features.  The
   *  new component will listen for feature change events and update itself.
   **/
  private void viewSelectedFeatureInfo () {
    if (!checkForSelectionFeatures (MAXIMUM_SELECTED_FEATURES,
                                    "really view statistics for " +
                                    "all (>" + MAXIMUM_SELECTED_FEATURES +
                                    ") selected features?")) {
      return;
    }

    final FeatureVector features_to_view = getSelection ().getAllFeatures ();

    for (int i = 0 ; i < features_to_view.size () ; ++i) {
      final Feature selection_feature = features_to_view.elementAt (i);

      new FeatureInfo (selection_feature, codon_usage_algorithm);
    }
  }

  /**
   *  View the bases of the selected features.  This creates a
   *  FeatureBaseViewer for each feature.
   *  @param include_numbers If true then the sequence will be numbered
   *    (every second line of the display will be numbers rather than
   *    sequence).
   **/
  private void viewSelectedFeatureBases (final boolean include_numbers) {
    if (!checkForSelectionFeatures (MAXIMUM_SELECTED_FEATURES,
                                    "really view bases for " +
                                    "all (>" + MAXIMUM_SELECTED_FEATURES +
                                    ") selected features?")) {
      return;
    }

    final FeatureVector features_to_view = getSelection ().getAllFeatures ();

    for (int i = 0 ; i < features_to_view.size () ; ++i) {
      final Feature this_feature = features_to_view.elementAt (i);

      new FeatureBaseViewer (this_feature, include_numbers);
    }
  }

  /**
   *  View the bases of the selected CDS features.  This creates a
   *  FeatureBaseViewer for each CDS feature.
   *  @param include_numbers If true then the amino acids will be numbered
   *    (every second line of the display will be numbers rather than
   *    sequence).
   **/
  private void viewSelectedFeatureAminoAcids (final boolean include_numbers) {
    if (!checkForSelectionFeatures (MAXIMUM_SELECTED_FEATURES,
                                    "really view amino acids for " +
                                    "all (>" + MAXIMUM_SELECTED_FEATURES +
                                    ") selected features?")) {
      return;
    }

    final FeatureVector features_to_view = getSelection ().getAllFeatures ();

    for (int i = 0 ; i < features_to_view.size () ; ++i) {
      final Feature this_feature = features_to_view.elementAt (i);

      new FeatureAminoAcidViewer (this_feature, include_numbers);
    }
  }

  /**
   *  Check that the number of features in the EntryGroup is less than the
   *  given number.  If it is less return true.  If not then popup a
   *  YesNoDialog asking for confirmation and return true if and only if the
   *  user chooses yes.
   **/
  private boolean checkEntryGroupSize (final int max_size) {
    final int feature_count = getEntryGroup ().getAllFeaturesCount ();

    if (feature_count < max_size) {
      return true;
    } else {
      final YesNoDialog dialog =
        new YesNoDialog (getParentFrame (),
                         "there are " + feature_count + " features in the " +
                         "active entries - continue?");

      return dialog.getResult ();
    }
  }

  /**
   *  Return the EntryGroup that was passed to the constructor.
   **/
  private EntryGroup getEntryGroup () {
    return entry_group;
  }

  private MenuItem feature_info_item = null;
  private MenuItem plot_features_item = null;
  private MenuItem view_feature_item = null;
  private MenuItem view_selection_item = null;
  private MenuItem view_fasta_item = null;
  private MenuItem view_feature_bases_item = null;
  private MenuItem view_feature_bases_as_fasta_item = null;
  private MenuItem view_feature_aa_item = null;
  private MenuItem view_feature_aa_as_fasta_item = null;
  private MenuItem overview_item = null;
  private MenuItem forward_overview_item = null;
  private MenuItem reverse_overview_item = null;
  private MenuItem view_cds_item = null;

  /**
   *  This object is used to calculate the codon usage statistic in
   *  FeatureInfo.
   **/
  private CodonUsageAlgorithm codon_usage_algorithm = null;

  /**
   *  The EntryGroup that was passed to the constructor.
   **/
  private EntryGroup entry_group = null;

  /**
   *  The Selection that was passed to the constructor.
   **/
  private Selection selection = null;

  /**
   *  The GotoEventSource that was passed to the constructor.
   **/
  private GotoEventSource goto_event_source = null;
}
