"README.md" did not exist on "269bfef8eee7a502dcb1b23c6b703b1ad8f0c32c"
Newer
Older
/* 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.
*
* $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/AlignmentViewer.java,v 1.43 2008-11-28 17:51:09 tjc Exp $
*/
package uk.ac.sanger.artemis.components;
import uk.ac.sanger.artemis.*;
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;
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.*;
import java.util.Comparator;
import java.util.Arrays;
import java.io.FileWriter;
import java.io.IOException;
/**
* This component shows an alignment of two sequences using the data from a
* ComparisonData object.
*
* @author Kim Rutherford
* @version $Id: AlignmentViewer.java,v 1.43 2008-11-28 17:51:09 tjc Exp $
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/** 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;
/** 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.
**/
private Vector<AlignmentSelectionChangeListener> selection_change_listeners =
new Vector<AlignmentSelectionChangeListener>();
/**
* 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. */
/** Blues used to display the percent identity of matches. */
/** 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;
/**
**/
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 Vector<AlignmentListener> alignment_event_listeners = new Vector<AlignmentListener> ();
/**
* 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 */
/** colour for reverse matches */
private Color revMatchColour = Color.blue;
/** colour for matches */
/**
* 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.
**/
public AlignmentViewer(final FeatureDisplay subject_feature_display,
final FeatureDisplay query_feature_display,
final ComparisonData comparison_data)
this.subject_feature_display = subject_feature_display;
this.query_feature_display = query_feature_display;
this.comparison_data = comparison_data;
subject_entry_group = getSubjectDisplay().getEntryGroup();
query_entry_group = getQueryDisplay().getEntryGroup();
final Bases subject_bases = getSubjectForwardStrand().getBases();
final Bases query_bases = getQueryForwardStrand().getBases();
final Selection subject_selection = getSubjectDisplay().getSelection();
final Selection query_selection = getQueryDisplay().getSelection();
public void selectionChanged(SelectionChangeEvent event)
{
if(frame == null)
frame = subject_feature_display.getParentFrame();
if(!frame.isVisible())
return;
final RangeVector ranges = subject_selection.getSelectionRanges();
selectFromSubjectRanges(ranges);
}
};
if(frame == null)
frame = query_feature_display.getParentFrame();
if(!frame.isVisible())
return;
final RangeVector ranges = query_selection.getSelectionRanges ();
subject_selection.addSelectionChangeListener(subject_listener);
query_selection.addSelectionChangeListener(query_listener);
subject_bases.addSequenceChangeListener(this, 0);
query_bases.addSequenceChangeListener(this, 0);
orig_subject_forward_strand = getSubjectForwardStrand();
orig_query_forward_strand = getQueryForwardStrand();
// on windows we have to check isPopupTrigger in mouseReleased(),
// but do it in mousePressed() on UNIX
if(isMenuTrigger(event))
popupMenu(event);
else
handleCanvasMousePress(event);
{
public void mouseDragged(final MouseEvent event)
{
if(isMenuTrigger(event))
return;
scroll_bar = new JScrollBar(Scrollbar.VERTICAL);
scroll_bar.setValues(1, 1, 1, 1000);
scroll_bar.addAdjustmentListener(new AdjustmentListener()
{
public void adjustmentValueChanged(AdjustmentEvent e)
{
}
/**
* Returns true if and only if the given MouseEvent should toggle the lock
* displays toggle.
**/
private boolean modifiersForLockToggle(final MouseEvent event)
{
return(event.getModifiers() & InputEvent.BUTTON2_MASK) != 0 ||
event.isAltDown();
}
/**
* Select those matches that overlap the given range on the subject
* sequence.
**/
public void selectFromSubjectRanges(final RangeVector select_ranges)
{
if(disable_selection_from_ranges)
final int all_matches_length = all_matches.length;
final int select_ranges_size = select_ranges.size();
final Strand current_subject_fwd_strand =
getSubjectForwardStrand();
final int subject_length = current_subject_fwd_strand.getSequenceLength();
for(int match_index = 0; match_index < all_matches_length; ++match_index)
final AlignMatch this_match = all_matches[match_index];
if(!isVisible(this_match))
continue;
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));
if(subject_sequence_end < subject_sequence_start)
{
final int tmp = subject_sequence_start;
subject_sequence_start = subject_sequence_end;
subject_sequence_end = tmp;
}
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 < subject_sequence_start
&& select_range_end < subject_sequence_start)
if(select_range_start > subject_sequence_end &&
select_range_end > subject_sequence_end)
//if(!selected_matches.contains(this_match))
selected_matches.add(this_match);
break;
if(selected_matches != null)
selectionChanged();
else
repaint();
}
/**
* Select those matches that overlap the given range on the query sequence.
**/
public void selectFromQueryRanges(final RangeVector select_ranges)
{
if(disable_selection_from_ranges)
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();
for(int match_index = 0; match_index < all_matches_length; ++match_index)
final AlignMatch this_match = all_matches[match_index];
if(!isVisible(this_match))
continue;
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));
if(query_sequence_end < query_sequence_start)
{
final int tmp = query_sequence_start;
query_sequence_start = query_sequence_end;
query_sequence_end = tmp;
}
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)
if(select_range_start > query_sequence_end &&
select_range_end > query_sequence_end)
//if(!selected_matches.contains(this_match))
selected_matches.add(this_match);
break;
if(selected_matches != null)
selectionChanged();
else
repaint();
}
/**
* Select the given match and move it to the top of the display.
**/
public void setSelection(final AlignMatch match)
{
selected_matches = new AlignMatchVector();
}
/**
* This method tells this AlignmentViewer component where the subject
* sequence is now.
**/
public void setSubjectSequencePosition(final DisplayAdjustmentEvent event)
{
}
/**
* This method tells this AlignmentViewer component where the query
* sequence is now.
**/
public void setQuerySequencePosition(final DisplayAdjustmentEvent event)
{
}
/**
* Implementation of the SequenceChangeListener interface. The display is
* redrawn if there is an event.
**/
public void sequenceChanged(final SequenceChangeEvent event)
{
}
/**
* Return true if and only if the given MouseEvent (a mouse press) should
* pop up a JPopupMenu.
**/
if( event.isPopupTrigger() ||
(event.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
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
{
if(!fc.getSelectedFile().canWrite())
{
JOptionPane.showMessageDialog(null,
"Cannot write to "+
fc.getSelectedFile().getCanonicalPath(),
"Warning",
JOptionPane.WARNING_MESSAGE);
return;
}
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());
{
public void actionPerformed (ActionEvent _)
{
if(selected_matches == null)
new MessageFrame("No matches selected").setVisible (true);
else
{
flip_subject_item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent _)
{
if(getSubjectDisplay().isRevCompDisplay())
getSubjectDisplay().setRevCompDisplay(false);
}
});
final JMenuItem flip_query_item =
new JMenuItem ("Flip Query Sequence");
popup.add (flip_query_item);
flip_query_item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent _)
{
if(getQueryDisplay().isRevCompDisplay())
getQueryDisplay().setRevCompDisplay(false);
else
getQueryDisplay().setRevCompDisplay(true);
final JMenuItem cutoffs_item = new JMenuItem("Set Score Cutoffs ...");
popup.add(cutoffs_item);
cutoffs_item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent _)
{
new ScoreChangeListener()
{
public void scoreChanged(final ScoreChangeEvent event)
{
minimum_score = event.getValue();
{
public void scoreChanged(final ScoreChangeEvent event)
{
maximum_score = event.getValue();
new ScoreChanger("Score Cutoffs",
minimum_listener, maximum_listener,
getComparisonData().getMinimumScore(),
getComparisonData().getMaximumScore());
score_changer.setVisible (true);
}
});
final JMenuItem percent_id_cutoffs_item =
new JMenuItem("Set Percent ID Cutoffs ...");
popup.add(percent_id_cutoffs_item);
percent_id_cutoffs_item.addActionListener(new ActionListener ()
{
public void actionPerformed(ActionEvent _)
{
new ScoreChangeListener()
{
public void scoreChanged(final ScoreChangeEvent event)
{
minimum_percent_id = event.getValue();
new ScoreChangeListener()
{
public void scoreChanged(final ScoreChangeEvent event)
{
maximum_percent_id = event.getValue();
new ScoreChanger("Percent Identity Cutoffs",
minimum_listener, maximum_listener,
0, 100);
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();
}
});
final JCheckBoxMenuItem sameColour =
new JCheckBoxMenuItem("Colour reverse & forward matches the same",reverseMatchColour);
sameColour.addItemListener(new ItemListener()
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();
JMenuItem colourRevMatches = new JMenuItem("Colour reverse matches...");
colourRevMatches.addActionListener(new ActionListener()
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();
}
}
});
popup.add(colourRevMatches);
}
new JCheckBoxMenuItem("Offer To RevComp", offer_to_flip_flag);
offer_to_flip_item.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
ignore_self_match_item.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent event)
{
ignore_self_match_flag = ignore_self_match_item.getState();
ignore_self_match_item.setState(ignore_self_match_flag);
add(popup);
popup.show(this, event.getX(), event.getY());
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" };
"Colour Selection",
JOptionPane.DEFAULT_OPTION,
JOptionPane.QUESTION_MESSAGE,null,
possibleValues, possibleValues[0]);
if(select == 0)
return newContentPane;
return null;
}
/**
* Handle a mouse press event on the drawing canvas - select on click,
* select and broadcast it on double click.
**/
private void handleCanvasMousePress(final MouseEvent event)
{
if(event.getID() != MouseEvent.MOUSE_PRESSED)
private void handleCanvasDoubleClick(final MouseEvent event)
{
}
/**
* Send an AlignmentEvent to all the AlignmentListeners.
* @param align_match The AlignMatch that we have just centred on.
**/
// copied from a book - synchronizing the whole method might cause a
// deadlock
targets = new Vector<AlignmentListener>(alignment_event_listeners);
final AlignmentListener listener = targets.elementAt(i);
listener.alignMatchChosen(new AlignmentEvent(align_match));
}
}
/**
* Handle a single click on the canvas.
**/
private void handleCanvasSingleClick(final MouseEvent event)
{
if(modifiersForLockToggle(event))
toggleDisplayLock();
else
{
if(!event.isShiftDown())
}
}
/**
* Add or remove the match at the given mouse position to the selection.
**/
if(clicked_align_match != null)
{
if(selected_matches == null)
{
selected_matches = new AlignMatchVector ();
selected_matches.add (clicked_align_match);
if(selected_matches.contains(clicked_align_match))
{
selected_matches.remove(clicked_align_match);
if(selected_matches.size() == 0)
}
/**
* 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.
**/
final int canvas_height = getSize().height;
final int canvas_width = getSize().width;
final int subject_length = getSubjectForwardStrand().getSequenceLength();
final int query_length = getQueryForwardStrand().getSequenceLength();
final boolean subject_flipped = subjectIsRevComp();
final boolean query_flipped = queryIsRevComp();
final float base_width = last_subject_event.getBaseWidth();
final float query_base_width = last_query_event.getBaseWidth();
final int subject_start = last_subject_event.getStart();
final int query_start = last_query_event.getStart();
final boolean subject_is_rev_comp = subjectIsRevComp();
subject_flipped, query_flipped, base_width, query_base_width, subject_start,
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];
// 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);
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)
* This method is called by setSelection() and others whenever the list of
* selected/highlighted hits changes. Calls alignmentSelectionChanged()
* on all interested AlignmentSelectionChangeListener objects, moves the
* selected matches to the top of the display and then calls