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 $
61
62
63
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
117
118
119
120
121
122
123
124
125
/** 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.
**/
/**
* 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;
/**
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
**/
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 */
/** 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)
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
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.
**/
final java.util.Vector targets;
// copied from a book - synchronizing the whole method might cause a
// deadlock
synchronized(this)
{
targets = (java.util.Vector)alignment_event_listeners.clone();
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)