Skip to content
Snippets Groups Projects
AlignmentViewer.java 74.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    /* AlignmentViewer.java
     *
     * created: Mon Jul 12 1999
     *
     * This file is part of Artemis
     *
     * Copyright (C) 1999,2000,2001  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.
     *
    
    tjc's avatar
    tjc committed
     * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/AlignmentViewer.java,v 1.43 2008-11-28 17:51:09 tjc Exp $
    
    tjc's avatar
    tjc committed
     */
    
    package uk.ac.sanger.artemis.components;
    
    import uk.ac.sanger.artemis.*;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.sequence.Strand;
    import uk.ac.sanger.artemis.sequence.Bases;
    import uk.ac.sanger.artemis.sequence.SequenceChangeListener;
    import uk.ac.sanger.artemis.sequence.SequenceChangeEvent;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.util.StringVector;
    import uk.ac.sanger.artemis.io.Range;
    import uk.ac.sanger.artemis.io.RangeVector;
    
    import java.awt.*;
    import java.awt.event.*;
    
    tjc's avatar
    tjc committed
    import java.util.Vector;
    
    import java.util.Comparator;
    import java.util.Arrays;
    
    tjc's avatar
    tjc committed
    import javax.swing.*;
    
    tjc's avatar
    tjc committed
    import java.io.FileWriter;
    import java.io.IOException;
    
    tjc's avatar
    tjc committed
    
    /**
     *  This component shows an alignment of two sequences using the data from a
     *  ComparisonData object.
     *
     *  @author Kim Rutherford
    
    tjc's avatar
    tjc committed
     *  @version $Id: AlignmentViewer.java,v 1.43 2008-11-28 17:51:09 tjc Exp $
    
    tjc's avatar
    tjc committed
     **/
    
    public class AlignmentViewer extends CanvasPanel
    
    tjc's avatar
    tjc committed
        implements SequenceChangeListener 
    {
    
    tjc's avatar
    tjc committed
      private static final long serialVersionUID = 1L;
    
    
    tjc's avatar
    tjc committed
      private Image offscreen;
    
    
    tjc's avatar
    tjc committed
      /** Comparison data that will be displayed in this component. */
      final private ComparisonData comparison_data;
    
      /** 
       *  All the AlignMatch objects from comparison_data (possibly in a
       *  different order.
       **/
      private AlignMatch[] all_matches = null;
    
      /**
       *  This is the last DisplayAdjustmentEvent reference that was passed to
       *  setSubjectSeqeuencePosition().
       **/
      private DisplayAdjustmentEvent last_subject_event;
    
      /**
       *  This is the last DisplayAdjustmentEvent reference that was passed to
       *  setQuerySeqeuencePosition().
       **/
      private DisplayAdjustmentEvent last_query_event;
    
      /** FeatureDisplay that is above this component.  (From the constructor). */
      private FeatureDisplay subject_feature_display;
    
      /** FeatureDisplay that is below this component.  (From the constructor). */
      private FeatureDisplay query_feature_display;
    
      /**
       *  Set by the constructor to be the original forward strand for the subject
       *  sequence.  This is use to determine whether to subject sequence has been
       *  reverse-complemented or not.
       **/
      private Strand orig_subject_forward_strand;
    
      /**
       *  Set by the constructor to be the original forward strand for the query
       *  sequence.  This is use to determine whether to query sequence has been
       *  reverse-complemented or not.
       **/
      private Strand orig_query_forward_strand;
    
      /**
       *  Set by the constructor to be the original reverse strand for the subject
       *  sequence.
       **/
      private Strand orig_subject_reverse_strand;
    
      /**
       *  Set by the constructor to be the original reverse strand for the query
       *  sequence.
       **/
      private Strand orig_query_reverse_strand;
    
      /** One of the two Entry objects that we are comparing. */
      final private EntryGroup subject_entry_group;
    
      /** One of the two Entry objects that we are comparing. */
      final private EntryGroup query_entry_group;
    
      /** Selected matches.  null means no matches are selected. */
      private AlignMatchVector selected_matches = null;
    
      /**
       *  The objects that are listening for AlignmentSelectionChangeEvents.
       **/
    
    tjc's avatar
    tjc committed
      private Vector selection_change_listeners = new Vector();
    
    tjc's avatar
    tjc committed
    
      /**
       *  The number of shades of red and blue to use for percentage ID colouring.
       **/
      private static int NUMBER_OF_SHADES = 13;
    
      /** Reds used to display the percent identity of matches.  */
    
    tjc's avatar
    tjc committed
      private Color[] red_percent_id_colours;
    
    tjc's avatar
    tjc committed
    
      /** Blues used to display the percent identity of matches. */
    
    tjc's avatar
    tjc committed
      private Color[] blue_percent_id_colours;
    
    tjc's avatar
    tjc committed
    
      /** Scroll bar used to set the minimum length of the visible matches. */
      private JScrollBar scroll_bar = null;
    
      /** Matches with scores below this value will not be shown. */
      private int minimum_score = 0;
    
      /** Matches with scores above this value will not be shown. */
      private int maximum_score = 99999999;
    
      /**
    
    tjc's avatar
    tjc committed
       *  Matches with percent id values below this number will not be shown.
    
    tjc's avatar
    tjc committed
       **/
      private int minimum_percent_id = 0;
    
      /**
    
    tjc's avatar
    tjc committed
       *  Matches with percent id values above this number will not be shown.
    
    tjc's avatar
    tjc committed
       **/
      private int maximum_percent_id = 100;
    
      /**
       *  True if we should offer to flip the query sequence when the user
       *  double clicks on a flipped match.
       **/
      private boolean offer_to_flip_flag = false;
    
      /**
       *  If true ignore self matches (ie query start == subject start && query
       *  end == subject end)
       **/
      private boolean ignore_self_match_flag = false;
    
      /** Vector of those objects that are listening for AlignmentEvents */
      private java.util.Vector alignment_event_listeners =
        new java.util.Vector ();
    
      /**
       *  If true then the FeatureDisplays above and below this AlignmentViewer
       *  should scroll together.
       **/
      private boolean displays_are_locked = true;
    
      /**
       *  Setting this to true will temporarily disable selectFromQueryRange() and
       *  selectFromSubjectRange() until enableSelection().  This is need to allow
       *  the selections of the top and bottom FeatureDisplays to be set without
       *  changing which AlignMatches are selected.
       **/
      private boolean disable_selection_from_ranges = false;
    
      /** user defined colours */
    
      private boolean reverseMatchColour = false;
    
    tjc's avatar
    tjc committed
      /** colour for reverse matches */
      private Color revMatchColour = Color.blue;
      /** colour for matches         */
    
    tjc's avatar
    tjc committed
      private Color matchColour    = Color.red;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
      /**
       *  Create a new AlignmentViewer for the given entries.
       *  @param subject_feature_display The FeatureDisplay that is above this
       *    component.
       *  @param query_feature_display The FeatureDisplay that is below this
       *    component.
       *  @param comparison_data Provides the AlignMatch objects that will be
       *    displayed.
       **/
    
    tjc's avatar
    tjc committed
      public AlignmentViewer(final FeatureDisplay subject_feature_display,
                             final FeatureDisplay query_feature_display,
                             final ComparisonData comparison_data) 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        this.subject_feature_display = subject_feature_display;
        this.query_feature_display   = query_feature_display;
        this.comparison_data         = comparison_data;
    
    tjc's avatar
    tjc committed
        this.all_matches             = getComparisonData().getMatches();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        subject_entry_group          = getSubjectDisplay().getEntryGroup();
        query_entry_group            = getQueryDisplay().getEntryGroup();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        final Bases subject_bases = getSubjectForwardStrand().getBases();
        final Bases query_bases   = getQueryForwardStrand().getBases();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        final Selection subject_selection = getSubjectDisplay().getSelection();
        final Selection query_selection = getQueryDisplay().getSelection();
    
    tjc's avatar
    tjc committed
    
        final SelectionChangeListener subject_listener =
    
    tjc's avatar
    tjc committed
          new SelectionChangeListener() 
        {
    
          JFrame frame = null;
    
    tjc's avatar
    tjc committed
          public void selectionChanged(SelectionChangeEvent event) 
          {
    
            if(frame == null)
              frame = subject_feature_display.getParentFrame();
            if(!frame.isVisible())
              return;
    
    
    tjc's avatar
    tjc committed
            final RangeVector ranges = subject_selection.getSelectionRanges();
            selectFromSubjectRanges(ranges);
          }
        };
    
    tjc's avatar
    tjc committed
    
        final SelectionChangeListener query_listener =
    
    tjc's avatar
    tjc committed
          new SelectionChangeListener() 
    
    tjc's avatar
    tjc committed
        {
    
          JFrame frame = null;
    
    tjc's avatar
    tjc committed
          public void selectionChanged (SelectionChangeEvent event) 
    
    tjc's avatar
    tjc committed
          {
    
            if(frame == null)
              frame = query_feature_display.getParentFrame();
            if(!frame.isVisible())
              return;
    
    
    tjc's avatar
    tjc committed
            final RangeVector ranges = query_selection.getSelectionRanges ();
    
    tjc's avatar
    tjc committed
            selectFromQueryRanges(ranges);
    
    tjc's avatar
    tjc committed
          }
        };
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        makeColours();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        subject_selection.addSelectionChangeListener(subject_listener);
        query_selection.addSelectionChangeListener(query_listener);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        subject_bases.addSequenceChangeListener(this, 0);
        query_bases.addSequenceChangeListener(this, 0);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        orig_subject_forward_strand = getSubjectForwardStrand();
        orig_query_forward_strand   = getQueryForwardStrand();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        addMouseListener(new MouseAdapter() 
    
    tjc's avatar
    tjc committed
        {
          public void mousePressed(final MouseEvent event) 
          {
    
    tjc's avatar
    tjc committed
            // on windows we have to check isPopupTrigger in mouseReleased(),
            // but do it in mousePressed() on UNIX
    
    tjc's avatar
    tjc committed
            if(isMenuTrigger(event)) 
              popupMenu(event);
            else 
              handleCanvasMousePress(event);
    
    tjc's avatar
    tjc committed
          }
        });
    
    
    tjc's avatar
    tjc committed
        addMouseMotionListener(new MouseMotionAdapter() 
    
    tjc's avatar
    tjc committed
        {
          public void mouseDragged(final MouseEvent event) 
          {
            if(isMenuTrigger(event))
              return;
    
    
    tjc's avatar
    tjc committed
            if(!modifiersForLockToggle(event))
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              if(!event.isShiftDown()) 
    
    tjc's avatar
    tjc committed
              {
    
    tjc's avatar
    tjc committed
                selected_matches = null;
    
    tjc's avatar
    tjc committed
                toggleSelection (event.getPoint());
    
    tjc's avatar
    tjc committed
              }
    
    tjc's avatar
    tjc committed
              repaint();
    
    tjc's avatar
    tjc committed
            }
          }
        });
    
    
    tjc's avatar
    tjc committed
        scroll_bar = new JScrollBar(Scrollbar.VERTICAL);
        scroll_bar.setValues(1, 1, 1, 1000);
    
    tjc's avatar
    tjc committed
        scroll_bar.setBlockIncrement(10);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        scroll_bar.addAdjustmentListener(new AdjustmentListener() 
        {
          public void adjustmentValueChanged(AdjustmentEvent e) 
          {
    
    tjc's avatar
    tjc committed
            repaint();
    
    tjc's avatar
    tjc committed
          }
        });
    
    
    tjc's avatar
    tjc committed
        maximum_score = getComparisonData().getMaximumScore();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        add(scroll_bar, "East");
        setBackground(Color.white);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Returns true if and only if the given MouseEvent should toggle the lock
       *  displays toggle.
       **/
    
    tjc's avatar
    tjc committed
      private boolean modifiersForLockToggle(final MouseEvent event) 
      {
        return(event.getModifiers() & InputEvent.BUTTON2_MASK) != 0 ||
          event.isAltDown();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Select those matches that overlap the given range on the subject
       *  sequence.
       **/
    
    tjc's avatar
    tjc committed
      public void selectFromSubjectRanges(final RangeVector select_ranges) 
      {
        if(disable_selection_from_ranges) 
    
    tjc's avatar
    tjc committed
          return;
    
        selected_matches = null;
    
    tjc's avatar
    tjc committed
        final int all_matches_length = all_matches.length;
        final int select_ranges_size = select_ranges.size();
    
        final Strand current_subject_fwd_strand =
                                  getSubjectForwardStrand();
    
    tjc's avatar
    tjc committed
    
    
        final int subject_length = current_subject_fwd_strand.getSequenceLength();
    
    tjc's avatar
    tjc committed
    
    
        for(int match_index = 0; match_index < all_matches_length; ++match_index)
    
    tjc's avatar
    tjc committed
        {
    
          final AlignMatch this_match = all_matches[match_index];
    
    tjc's avatar
    tjc committed
    
    
          if(!isVisible(this_match))
            continue;
    
    tjc's avatar
    tjc committed
    
    
          int subject_sequence_start = getRealSubjectSequenceStart(this_match,
              subject_length,
              (getOrigSubjectForwardStrand() != current_subject_fwd_strand));
          int subject_sequence_end = getRealSubjectSequenceEnd(this_match,
              subject_length,
              (getOrigSubjectForwardStrand() != current_subject_fwd_strand));
    
    tjc's avatar
    tjc committed
    
    
          if(subject_sequence_end < subject_sequence_start)
          {
            final int tmp = subject_sequence_start;
            subject_sequence_start = subject_sequence_end;
            subject_sequence_end = tmp;
          }
    
    tjc's avatar
    tjc committed
    
    
          for(int range_index = 0; range_index < select_ranges_size; ++range_index)
          {
            final Range select_range = (Range) select_ranges.elementAt(range_index);
            final int select_range_start = select_range.getStart();
            final int select_range_end = select_range.getEnd();
    
    tjc's avatar
    tjc committed
    
    
            if(select_range_start < subject_sequence_start
                && select_range_end < subject_sequence_start)
    
    tjc's avatar
    tjc committed
              continue;
    
    
    tjc's avatar
    tjc committed
            if(select_range_start > subject_sequence_end &&
               select_range_end   > subject_sequence_end) 
    
    tjc's avatar
    tjc committed
              continue;
    
    
    tjc's avatar
    tjc committed
            if(selected_matches == null) 
    
    tjc's avatar
    tjc committed
              selected_matches = new AlignMatchVector();
    
    tjc's avatar
    tjc committed
    
    
            //if(!selected_matches.contains(this_match)) 
            selected_matches.add(this_match);
            break;
    
    tjc's avatar
    tjc committed
          }
        }
    
    
    tjc's avatar
    tjc committed
        if(selected_matches != null)
          selectionChanged();
        else
          repaint();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Select those matches that overlap the given range on the query sequence.
       **/
    
    tjc's avatar
    tjc committed
      public void selectFromQueryRanges(final RangeVector select_ranges) 
      {
        if(disable_selection_from_ranges) 
    
    tjc's avatar
    tjc committed
          return;
    
        selected_matches = null;
    
    tjc's avatar
    tjc committed
        final int select_ranges_size = select_ranges.size();
        final int all_matches_length = all_matches.length;
        final Strand current_query_forward_strand = getQueryForwardStrand();
        final int query_length =
              current_query_forward_strand.getSequenceLength();
    
    tjc's avatar
    tjc committed
    
    
        for(int match_index = 0; match_index < all_matches_length; ++match_index)
    
    tjc's avatar
    tjc committed
        {
    
          final AlignMatch this_match = all_matches[match_index];
    
    tjc's avatar
    tjc committed
    
    
          if(!isVisible(this_match))
            continue;
    
    tjc's avatar
    tjc committed
    
    
          int query_sequence_start = getRealQuerySequenceStart(this_match,
              query_length,
              (getOrigQueryForwardStrand() != current_query_forward_strand));
          int query_sequence_end = getRealQuerySequenceEnd(this_match,
              query_length,
              (getOrigQueryForwardStrand() != current_query_forward_strand));
    
    tjc's avatar
    tjc committed
    
    
          if(query_sequence_end < query_sequence_start)
          {
            final int tmp = query_sequence_start;
            query_sequence_start = query_sequence_end;
            query_sequence_end = tmp;
          }
    
    tjc's avatar
    tjc committed
    
    
          for(int range_index = 0; range_index < select_ranges_size; ++range_index)
          {
            final Range select_range = (Range) select_ranges.elementAt(range_index);
            final int select_range_start = select_range.getStart();
            final int select_range_end = select_range.getEnd();
            if(select_range_start < query_sequence_start
                && select_range_end < query_sequence_start) 
    
    tjc's avatar
    tjc committed
              continue;
    
    
    tjc's avatar
    tjc committed
            if(select_range_start > query_sequence_end &&
               select_range_end > query_sequence_end) 
    
    tjc's avatar
    tjc committed
              continue;
    
    
    tjc's avatar
    tjc committed
            if(selected_matches == null) 
    
    tjc's avatar
    tjc committed
              selected_matches = new AlignMatchVector();
    
    tjc's avatar
    tjc committed
    
    
            //if(!selected_matches.contains(this_match)) 
            selected_matches.add(this_match);
            break;
    
    tjc's avatar
    tjc committed
          }
        }
    
    tjc's avatar
    tjc committed
        if(selected_matches != null)
          selectionChanged();
        else
          repaint();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Select the given match and move it to the top of the display.
       **/
    
    tjc's avatar
    tjc committed
      public void setSelection(final AlignMatch match) 
      {
        selected_matches = new AlignMatchVector();
    
    tjc's avatar
    tjc committed
        selected_matches.add(match);
    
    tjc's avatar
    tjc committed
        selectionChanged();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  This method tells this AlignmentViewer component where the subject
       *  sequence is now.
       **/
    
    tjc's avatar
    tjc committed
      public void setSubjectSequencePosition(final DisplayAdjustmentEvent event) 
      {
    
    tjc's avatar
    tjc committed
        last_subject_event = event;
    
    tjc's avatar
    tjc committed
        repaint();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  This method tells this AlignmentViewer component where the query
       *  sequence is now.
       **/
    
    tjc's avatar
    tjc committed
      public void setQuerySequencePosition(final DisplayAdjustmentEvent event) 
      {
    
    tjc's avatar
    tjc committed
        last_query_event = event;
    
    tjc's avatar
    tjc committed
        repaint();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Implementation of the SequenceChangeListener interface.  The display is
       *  redrawn if there is an event.
       **/
    
    tjc's avatar
    tjc committed
      public void sequenceChanged(final SequenceChangeEvent event) 
      {
    
    tjc's avatar
    tjc committed
        repaint();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return true if and only if the given MouseEvent (a mouse press) should
       *  pop up a JPopupMenu.
       **/
    
    tjc's avatar
    tjc committed
      private boolean isMenuTrigger(final MouseEvent event) 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        if( event.isPopupTrigger() ||
           (event.getModifiers() & InputEvent.BUTTON3_MASK) != 0) 
    
    tjc's avatar
    tjc committed
          return true;
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          return false;
      }
    
      /**
       *  Popup a menu.
       **/
    
    tjc's avatar
    tjc committed
      private void popupMenu(final MouseEvent event) 
      {
    
    tjc's avatar
    tjc committed
        final JPopupMenu popup = new JPopupMenu();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
    
        final JMenuItem save_matches = new JMenuItem("Save Comparison File...");
        save_matches.addActionListener(new ActionListener()
        {
          public void actionPerformed (ActionEvent _)
          {
            StickyFileChooser fc = new StickyFileChooser();
    
            int returnVal = fc.showSaveDialog(null);  
            if(returnVal != JFileChooser.APPROVE_OPTION) 
              return;
            else if(fc.getSelectedFile().exists())
            {
              Object[] possibleValues = { "YES", "NO" };
              int select = JOptionPane.showOptionDialog(null, 
                                     fc.getSelectedFile().getName()+"\n"+
                                     "exists. Overwrite?",
                                     "File Exists",
                                     JOptionPane.DEFAULT_OPTION,
                                     JOptionPane.QUESTION_MESSAGE,null,
                                     possibleValues, possibleValues[0]);
              if(select == 1)
                return;  
            }
    
            try
            {
    
    tjc's avatar
    tjc committed
    /*
    
    tjc's avatar
    tjc committed
              if(!fc.getSelectedFile().canWrite())
              {
                JOptionPane.showMessageDialog(null,
                            "Cannot write to "+
                            fc.getSelectedFile().getCanonicalPath(),
                            "Warning",
                            JOptionPane.WARNING_MESSAGE);
                return;
              }
    
    tjc's avatar
    tjc committed
    */
    
    tjc's avatar
    tjc committed
              final FileWriter out_writer = new FileWriter(fc.getSelectedFile());
              final String query = getQueryEntryGroup().getDefaultEntry().getName();
              final String subject = getSubjectEntryGroup().getDefaultEntry().getName();
    
              for(int i = 0; i < all_matches.length; ++i)
                MSPcrunchComparisonData.writeMatchFromAlignMatch(all_matches[i],
                                                   query, subject,
                                                   out_writer);
              out_writer.close();
            }
            catch(IOException ioe)
            {
              JOptionPane.showMessageDialog(null,
                            "Error writing out comparison file.", 
                            "Warning",
                            JOptionPane.WARNING_MESSAGE);
              ioe.printStackTrace();
            }
          }
        });
        popup.add(save_matches);
        popup.add(new JSeparator());
    
    
    tjc's avatar
    tjc committed
        final JMenuItem alignmatch_list_item =
    
    tjc's avatar
    tjc committed
          new JMenuItem("View Selected Matches");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        popup.add(alignmatch_list_item);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        alignmatch_list_item.addActionListener(new ActionListener() 
    
    tjc's avatar
    tjc committed
        {
          public void actionPerformed (ActionEvent _) 
          {
            if(selected_matches == null) 
              new MessageFrame("No matches selected").setVisible (true);
            else 
            {
    
    tjc's avatar
    tjc committed
              final AlignMatchVector matches =
    
    tjc's avatar
    tjc committed
                (AlignMatchVector)selected_matches.clone();
    
    tjc's avatar
    tjc committed
    
              final AlignMatchViewer viewer =
    
    tjc's avatar
    tjc committed
                new AlignMatchViewer(AlignmentViewer.this, matches);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
              viewer.setVisible(true);
    
    tjc's avatar
    tjc committed
            }
          }
        });
    
        final JMenuItem flip_subject_item =
    
    tjc's avatar
    tjc committed
          new JMenuItem("Flip Subject Sequence");
    
    tjc's avatar
    tjc committed
    
        popup.add (flip_subject_item);
    
    
    tjc's avatar
    tjc committed
        flip_subject_item.addActionListener(new ActionListener() 
        {
          public void actionPerformed(ActionEvent _) 
          {
    
    tjc's avatar
    tjc committed
            if(getSubjectDisplay().isRevCompDisplay()) 
              getSubjectDisplay().setRevCompDisplay(false);
    
    tjc's avatar
    tjc committed
            else 
    
    tjc's avatar
    tjc committed
              getSubjectDisplay().setRevCompDisplay(true);
    
    tjc's avatar
    tjc committed
          }
        });
    
        final JMenuItem flip_query_item =
          new JMenuItem ("Flip Query Sequence");
    
        popup.add (flip_query_item);
    
    
    tjc's avatar
    tjc committed
        flip_query_item.addActionListener(new ActionListener() 
        {
          public void actionPerformed(ActionEvent _) 
          {
            if(getQueryDisplay().isRevCompDisplay())
              getQueryDisplay().setRevCompDisplay(false);
            else
              getQueryDisplay().setRevCompDisplay(true);
    
    tjc's avatar
    tjc committed
          }
        });
    
    
    tjc's avatar
    tjc committed
        final JMenuItem cutoffs_item = new JMenuItem("Set Score Cutoffs ...");
        popup.add(cutoffs_item);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        cutoffs_item.addActionListener(new ActionListener() 
        {
          public void actionPerformed(ActionEvent _) 
          {
    
    tjc's avatar
    tjc committed
            final ScoreChangeListener minimum_listener =
    
    tjc's avatar
    tjc committed
              new ScoreChangeListener() 
            {
              public void scoreChanged(final ScoreChangeEvent event)
              {
                minimum_score = event.getValue();
    
    tjc's avatar
    tjc committed
                repaint();
    
    tjc's avatar
    tjc committed
              }
            };
    
    tjc's avatar
    tjc committed
    
            final ScoreChangeListener maximum_listener =
    
    tjc's avatar
    tjc committed
              new ScoreChangeListener()
    
    tjc's avatar
    tjc committed
            {
              public void scoreChanged(final ScoreChangeEvent event) 
              {
                maximum_score = event.getValue();
    
    tjc's avatar
    tjc committed
                repaint();
    
    tjc's avatar
    tjc committed
              }
            };
    
    tjc's avatar
    tjc committed
    
            final ScoreChanger score_changer =
    
    tjc's avatar
    tjc committed
              new ScoreChanger("Score Cutoffs",
                               minimum_listener, maximum_listener,
                               getComparisonData().getMinimumScore(),
                               getComparisonData().getMaximumScore());
    
    tjc's avatar
    tjc committed
    
            score_changer.setVisible (true);
          }
        });
    
        final JMenuItem percent_id_cutoffs_item =
    
    tjc's avatar
    tjc committed
          new JMenuItem("Set Percent ID Cutoffs ...");
        popup.add(percent_id_cutoffs_item);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        percent_id_cutoffs_item.addActionListener(new ActionListener () 
        {
          public void actionPerformed(ActionEvent _) 
          {
    
    tjc's avatar
    tjc committed
            final ScoreChangeListener minimum_listener =
    
    tjc's avatar
    tjc committed
              new ScoreChangeListener()
            {
              public void scoreChanged(final ScoreChangeEvent event) 
              {
                minimum_percent_id = event.getValue();
    
    tjc's avatar
    tjc committed
                repaint();
    
    tjc's avatar
    tjc committed
              }
            };
    
    tjc's avatar
    tjc committed
    
            final ScoreChangeListener maximum_listener =
    
    tjc's avatar
    tjc committed
              new ScoreChangeListener() 
            {
              public void scoreChanged(final ScoreChangeEvent event) 
              {
                maximum_percent_id = event.getValue();
    
    tjc's avatar
    tjc committed
                repaint();
    
    tjc's avatar
    tjc committed
              }
            };
    
    tjc's avatar
    tjc committed
    
            final ScoreChanger score_changer =
    
    tjc's avatar
    tjc committed
              new ScoreChanger("Percent Identity Cutoffs",
                               minimum_listener, maximum_listener,
                               0, 100);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            score_changer.setVisible(true);
    
    tjc's avatar
    tjc committed
          }
        });
    
    
    tjc's avatar
    tjc committed
    
        final JCheckBoxMenuItem lock_item = new JCheckBoxMenuItem("Lock Sequences");
        lock_item.setSelected(displaysAreLocked());
        popup.add(lock_item);
    
        lock_item.addItemListener(new ItemListener()
        {
          public void itemStateChanged(ItemEvent e)
          {
            if(lock_item.isSelected())
              lockDisplays();
            else
              unlockDisplays();
          }
        });
    
    
    tjc's avatar
    tjc committed
        popup.addSeparator();
    
    
        final JCheckBoxMenuItem sameColour = 
           new JCheckBoxMenuItem("Colour reverse & forward matches the same",reverseMatchColour);
        sameColour.addItemListener(new ItemListener()
    
    tjc's avatar
    tjc committed
        {
          public void itemStateChanged(ItemEvent e)
          {
    
            reverseMatchColour = sameColour.getState();
    
    tjc's avatar
    tjc committed
            repaint();
    
    tjc's avatar
    tjc committed
          }
        });
    
        popup.add(sameColour);
    
    tjc's avatar
    tjc committed
    
        JMenuItem colourMatches = new JMenuItem("Colour matches...");
        colourMatches.addActionListener(new ActionListener()
        {
          public void actionPerformed(ActionEvent event)
          {
    
            ColorChooserShades shades = createColours("Colour Matches", 
                                                      red_percent_id_colours[NUMBER_OF_SHADES-1]);
            if(shades != null)
            {
              red_percent_id_colours = shades.getDefinedColour();
    
    tjc's avatar
    tjc committed
              repaint();
    
    tjc's avatar
    tjc committed
          }
        });
        popup.add(colourMatches);
    
    
        if(!reverseMatchColour)
    
    tjc's avatar
    tjc committed
        {
    
          JMenuItem colourRevMatches = new JMenuItem("Colour reverse matches...");
          colourRevMatches.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent event)
            {
              ColorChooserShades shades = createColours("Colour Reverse Matches",
                                                       blue_percent_id_colours[NUMBER_OF_SHADES-1]);
              if(shades != null)
              {
                blue_percent_id_colours = shades.getDefinedColour();
    
    tjc's avatar
    tjc committed
                repaint();
    
              }
            }
          });
          popup.add(colourRevMatches);
        }
    
    
    tjc's avatar
    tjc committed
        popup.addSeparator();
    
    tjc's avatar
    tjc committed
    
        final JCheckBoxMenuItem offer_to_flip_item =
    
    tjc's avatar
    tjc committed
          new JCheckBoxMenuItem("Offer To RevComp", offer_to_flip_flag);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        offer_to_flip_item.addItemListener(new ItemListener() 
        {
          public void itemStateChanged(ItemEvent e) 
          {
    
    tjc's avatar
    tjc committed
            offer_to_flip_flag = !offer_to_flip_flag;
          }
        });
    
    
    tjc's avatar
    tjc committed
        popup.add(offer_to_flip_item);
    
    tjc's avatar
    tjc committed
    
        final JCheckBoxMenuItem ignore_self_match_item =
    
    tjc's avatar
    tjc committed
          new JCheckBoxMenuItem("Ignore Self Matches");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        ignore_self_match_item.addItemListener(new ItemListener() 
        {
          public void itemStateChanged(ItemEvent event)
          {
            ignore_self_match_flag = ignore_self_match_item.getState();
    
    tjc's avatar
    tjc committed
            repaint();
    
    tjc's avatar
    tjc committed
          }
        });
    
    
    tjc's avatar
    tjc committed
        ignore_self_match_item.setState(ignore_self_match_flag);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        popup.add(ignore_self_match_item);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        add(popup);
        popup.show(this, event.getX(), event.getY());
    
    tjc's avatar
    tjc committed
      }
    
    
    
      private ColorChooserShades createColours(String title, Color initialColour) 
      {
        //Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(true);
    
        //Create and set up the window.
        JFrame frame = new JFrame("ColorChooserDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        //Create and set up the content pane.
        ColorChooserShades newContentPane = new ColorChooserShades(title,initialColour);
    
        Object[] possibleValues = { "OK", "CANCEL" };
    
    tjc's avatar
    tjc committed
        int select = JOptionPane.showOptionDialog(null, newContentPane,
    
                                     "Colour Selection",
                                     JOptionPane.DEFAULT_OPTION, 
                                     JOptionPane.QUESTION_MESSAGE,null,
                                     possibleValues, possibleValues[0]);
        if(select == 0)
          return newContentPane;
    
        return null;
      }
    
    
    
    tjc's avatar
    tjc committed
      /**
       *  Handle a mouse press event on the drawing canvas - select on click,
       *  select and broadcast it on double click.
       **/
    
    tjc's avatar
    tjc committed
      private void handleCanvasMousePress(final MouseEvent event) 
      {
        if(event.getID() != MouseEvent.MOUSE_PRESSED) 
    
    tjc's avatar
    tjc committed
          return;
    
    
    tjc's avatar
    tjc committed
        if(event.getClickCount() == 2) 
    
    tjc's avatar
    tjc committed
          handleCanvasDoubleClick(event);
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          handleCanvasSingleClick(event);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        repaint();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Handle a double click on the canvas.
       **/
    
    tjc's avatar
    tjc committed
      private void handleCanvasDoubleClick(final MouseEvent event) 
      {
    
    tjc's avatar
    tjc committed
        // there should be only one match in the array
    
    tjc's avatar
    tjc committed
        if(selected_matches != null) 
    
    tjc's avatar
    tjc committed
          alignAt(selected_matches.elementAt(0));
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Send an AlignmentEvent to all the AlignmentListeners.
       *  @param align_match The AlignMatch that we have just centred on.
       **/
    
    tjc's avatar
    tjc committed
      public void alignAt(final AlignMatch align_match) 
      {
    
    tjc's avatar
    tjc committed
        final java.util.Vector targets;
        // copied from a book - synchronizing the whole method might cause a
        // deadlock
    
    tjc's avatar
    tjc committed
        synchronized(this) 
        {
          targets = (java.util.Vector)alignment_event_listeners.clone();
    
    tjc's avatar
    tjc committed
        }
    
    
    tjc's avatar
    tjc committed
        for(int i = 0; i < targets.size(); ++i) 
        {
    
    tjc's avatar
    tjc committed
          final AlignmentListener listener =
    
    tjc's avatar
    tjc committed
            (AlignmentListener)targets.elementAt(i);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          listener.alignMatchChosen(new AlignmentEvent(align_match));
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Handle a single click on the canvas.
       **/
    
    tjc's avatar
    tjc committed
      private void handleCanvasSingleClick(final MouseEvent event) 
      {
        if(modifiersForLockToggle(event)) 
          toggleDisplayLock();
        else 
        {
          if(!event.isShiftDown()) 
    
    tjc's avatar
    tjc committed
            selected_matches = null;
    
    tjc's avatar
    tjc committed
          
    
    tjc's avatar
    tjc committed
          toggleSelection(event.getPoint());
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Add or remove the match at the given mouse position to the selection.
       **/
    
    tjc's avatar
    tjc committed
      private void toggleSelection(final Point point) 
      {
    
    tjc's avatar
    tjc committed
        final AlignMatch clicked_align_match =
    
    tjc's avatar
    tjc committed
          getAlignMatchFromPosition(point);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        if(clicked_align_match != null) 
        {
          if(selected_matches == null) 
          {
    
    tjc's avatar
    tjc committed
            selected_matches = new AlignMatchVector ();
            selected_matches.add (clicked_align_match);
    
    tjc's avatar
    tjc committed
          } 
          else 
          {
    
    tjc's avatar
    tjc committed
            if(selected_matches.contains(clicked_align_match)) 
            {
              selected_matches.remove(clicked_align_match);
              if(selected_matches.size() == 0)
    
    tjc's avatar
    tjc committed
                selected_matches = null;
            }
    
    tjc's avatar
    tjc committed
            else
              selected_matches.add(clicked_align_match);
    
    tjc's avatar
    tjc committed
          }
        }
    
    
    tjc's avatar
    tjc committed
        selectionChanged();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return the AlignMatch at the given Point on screen or null if there is
       *  no match at that point.  The alignment_data_array is searched in reverse
       *  order.
       **/
    
    tjc's avatar
    tjc committed
      private AlignMatch getAlignMatchFromPosition(final Point click_point) 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        final int canvas_height = getSize().height;
        final int canvas_width  = getSize().width;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        final int subject_length = getSubjectForwardStrand().getSequenceLength();
        final int query_length   = getQueryForwardStrand().getSequenceLength();
    
        final boolean subject_flipped = subjectIsRevComp();
        final boolean query_flipped   = queryIsRevComp();
    
    
    tjc's avatar
    tjc committed
        final int all_matches_length = all_matches.length;
    
    tjc's avatar
    tjc committed
        final float base_width       = last_subject_event.getBaseWidth();
        final float query_base_width = last_query_event.getBaseWidth();
        
    
    tjc's avatar
    tjc committed
        final int subject_start = last_subject_event.getStart();
        final int query_start   = last_query_event.getStart();
    
    tjc's avatar
    tjc committed
        final boolean subject_is_rev_comp = subjectIsRevComp();
    
    tjc's avatar
    tjc committed
        final boolean query_is_rev_comp   = queryIsRevComp();
    
    tjc's avatar
    tjc committed
        boolean is_rev_match;
    
    tjc's avatar
    tjc committed
        int[] match_x_positions;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        for(int i = all_matches_length - 1; i >= 0 ; --i) 
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          final AlignMatch this_match = all_matches [i];
    
    
    tjc's avatar
    tjc committed
          is_rev_match = this_match.isRevMatch();
    
    tjc's avatar
    tjc committed
          match_x_positions =
    
    tjc's avatar
    tjc committed
            getMatchCoords(canvas_width, this_match, subject_length, query_length,
    
    tjc's avatar
    tjc committed
                           subject_flipped, query_flipped, base_width, query_base_width, subject_start, 
    
    tjc's avatar
    tjc committed
                           query_start, subject_is_rev_comp, query_is_rev_comp, is_rev_match);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          if(match_x_positions == null) 
    
    tjc's avatar
    tjc committed
            continue;
    
    
    tjc's avatar
    tjc committed
          if(!isVisible(this_match)) 
    
    tjc's avatar
    tjc committed
            continue;
    
          final int subject_start_x = match_x_positions[0];
    
    tjc's avatar
    tjc committed
          final int subject_end_x   = match_x_positions[1];
          final int query_start_x   = match_x_positions[2];
          final int query_end_x     = match_x_positions[3];
    
    tjc's avatar
    tjc committed
    
          // this is the x coordinate of the point where the line y = click_point
          // hits the left edge of the match box
          final double match_left_x =
            subject_start_x +
            (1.0 * (query_start_x - subject_start_x)) *
            (1.0 * click_point.y / canvas_height);
    
          // this is the x coordinate of the point where the line y = click_point
          // hits the right edge of the match box
          final double match_right_x =
            subject_end_x +
            (1.0 * (query_end_x - subject_end_x)) *
            (1.0 * click_point.y / canvas_height);
    
    
    tjc's avatar
    tjc committed
          if(click_point.x >= match_left_x - 1 &&
             click_point.x <= match_right_x + 1 ||
             click_point.x <= match_left_x + 1 &&
             click_point.x >= match_right_x - 1) 
    
    tjc's avatar
    tjc committed
            return this_match;
        }
    
        return null;
      }