/* GeneViewerPanel * * created: 2006 * * This file is part of Artemis * * Copyright(C) 2006 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/genebuilder/GeneViewerPanel.java,v 1.85 2009-06-18 14:59:05 tjc Exp $ */ package uk.ac.sanger.artemis.components.genebuilder; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.List; import java.util.Hashtable; import java.util.Set; import java.util.Iterator; import java.util.Vector; import uk.ac.sanger.artemis.Entry; import uk.ac.sanger.artemis.EntryGroup; import uk.ac.sanger.artemis.FeatureSegment; import uk.ac.sanger.artemis.FeatureSegmentVector; import uk.ac.sanger.artemis.LastSegmentException; import uk.ac.sanger.artemis.Options; import uk.ac.sanger.artemis.Selection; import uk.ac.sanger.artemis.FeatureVector; import uk.ac.sanger.artemis.components.SegmentBorder; import uk.ac.sanger.artemis.io.DatabaseInferredFeature; import uk.ac.sanger.artemis.io.EntryInformationException; import uk.ac.sanger.artemis.io.Feature; import uk.ac.sanger.artemis.io.GFFStreamFeature; import uk.ac.sanger.artemis.io.ChadoCanonicalGene; import uk.ac.sanger.artemis.io.InvalidRelationException; import uk.ac.sanger.artemis.io.Key; import uk.ac.sanger.artemis.io.Qualifier; import uk.ac.sanger.artemis.io.QualifierVector; import uk.ac.sanger.artemis.io.Location; import uk.ac.sanger.artemis.io.Range; import uk.ac.sanger.artemis.io.RangeVector; import uk.ac.sanger.artemis.sequence.Marker; import uk.ac.sanger.artemis.sequence.MarkerRange; import uk.ac.sanger.artemis.sequence.Strand; import uk.ac.sanger.artemis.util.DatabaseDocument; import uk.ac.sanger.artemis.util.OutOfRangeException; import uk.ac.sanger.artemis.util.ReadOnlyException; public class GeneViewerPanel extends MapPanel { /** */ private static final long serialVersionUID = 1L; /** pop up menu */ private JPopupMenu popup; private float fraction; private int start; private MarkerRange click_range = null; /** * The last(FeatureSegment) Marker that the user clicked on. This is used * for dragging the ends of segments. **/ private Marker click_segment_marker = null; /** * This is true if click_segment_marker is the Marker at the start of * segment false otherwise. The value is only useful if * click_segment_marker is set. **/ private boolean click_segment_marker_is_start_marker = false; /** * When a FeatureSegment Marker drag starts, this is set to the Marker at * the other end of the segment. This is used to check that the drag has * not move the Marker too far(past the end of the segment). **/ private Marker other_end_of_segment_marker = null; /** This is a record of the feature last clicked */ private uk.ac.sanger.artemis.Feature clicked_feature; private Point last_cursor_position; /** * The shortcut for Delete Selected Features. **/ final static KeyStroke DELETE_FEATURES_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); final static public int DELETE_FEATURES_KEY_CODE = KeyEvent.VK_DELETE; final static KeyStroke CREATE_FEATURES_KEY = KeyStroke.getKeyStroke (KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); final static public int CREATE_FEATURES_KEY_CODE = KeyEvent.VK_C; private GeneBuilderFrame gene_builder; public GeneViewerPanel(final ChadoCanonicalGene chado_gene, final Selection selection, final EntryGroup entry_group, final GeneBuilderFrame gene_builder, final JLabel status_line) { this.chado_gene = chado_gene; this.selection = selection; this.gene_builder = gene_builder; Dimension dim = new Dimension(400,350); setPreferredSize(dim); setBackground(Color.white); // Popup menu addMouseListener(new PopupListener()); popup = new JPopupMenu(); createMenus(popup, entry_group); // Listen for mouse motion events so that we can select ranges of bases. addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent event) { if(event.isPopupTrigger() || entry_group == null || event.getButton() == MouseEvent.BUTTON3) return; int select_start = (int) ((event.getX() - border) / fraction) + start; final Strand strand = ((uk.ac.sanger.artemis.Feature) (chado_gene.getGene() .getUserData())).getStrand(); if(!strand.isForwardStrand()) select_start = strand.getBases().getComplementPosition(select_start); if(click_segment_marker != null) { final int new_position; MarkerRange drag_range; try { drag_range = new MarkerRange(strand, select_start, select_start + 1); } catch(OutOfRangeException e1) { e1.printStackTrace(); return; } if(click_segment_marker_is_start_marker) { // the Marker is at the start of the segment new_position = drag_range.getStart().getPosition(); // don't go past the other end of the segment if(new_position > other_end_of_segment_marker.getPosition()) return; } else { new_position = drag_range.getEnd().getPosition(); // don't go past the other end of the segment if(new_position < other_end_of_segment_marker.getPosition()) return; } try { click_segment_marker.setPosition(new_position); gene_builder.setActiveFeature(clicked_feature, false); return; } catch(OutOfRangeException e) { throw new Error("internal error - unexpected OutOfRangeException"); } } final MarkerRange selected_range = selection.getMarkerRange(); try { MarkerRange drag_range = new MarkerRange(strand, select_start, select_start + 1); // final MarkerRange new_marker_range; if(selected_range == null || click_range == null) { click_range = drag_range; status_line.setText(""); } else { click_range = selected_range.combineRanges(drag_range, true); status_line.setText(selected_range.getRawRange().getStart() + ".." + selected_range.getRawRange().getEnd()); } last_cursor_position = event.getPoint(); selection.setMarkerRange(click_range); repaint(); } catch(OutOfRangeException e) { e.printStackTrace(); } } }); } /** * Create menu for gene editor * * @param menu * @param entry_group */ protected void createMenus(JComponent menu, final EntryGroup entry_group) { JMenuItem deleteMenu = new JMenuItem("Delete Selected Features"); deleteMenu.setAccelerator(DELETE_FEATURES_KEY); deleteMenu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { FeatureVector features = selection.getAllFeatures(); boolean delete = deleteFeatures(features, chado_gene); try { if(delete) { for(int i = 0; i < features.size(); i++) GeneUtils.deleteAllFeature(features.elementAt(i), chado_gene); repaint(); } else gene_builder.getFeatureEdit().setObsoleteChanged(true); } catch(ReadOnlyException e) { JOptionPane.showMessageDialog(null, e.getMessage(), "Read Only", JOptionPane.WARNING_MESSAGE); } } }); menu.add(deleteMenu); final JMenuItem deleteSegmentMenu = new JMenuItem("Delete Selected Exon"); deleteSegmentMenu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { final FeatureSegmentVector features = selection.getAllSegments(); if(features == null || features.size() < 1) { JOptionPane.showMessageDialog(null, "Select an exon and try again.", "Exon to delete not found!", JOptionPane.ERROR_MESSAGE); return; } else { for(int i=0; i 0) embl_exon = (GFFStreamFeature)exons.get(0); Range range_selected = selection.getSelectionRange(); addExonFeature(chado_gene, entry_group, embl_exon, range_selected, uniquename, selection, exonKey, gene_builder); } }); createFeatureMenu.add(createExon); JMenuItem createFeature5Utr = new JMenuItem("5'UTR"); createFeature5Utr.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { addDnaFeature(last_cursor_position, selection, entry_group, new Key("five_prime_UTR")); } }); JMenuItem createFeature3Utr = new JMenuItem("3'UTR"); createFeature3Utr.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { addDnaFeature(last_cursor_position, selection, entry_group, new Key("three_prime_UTR")); } }); JMenuItem createFeatureDna = new JMenuItem("region"); createFeatureDna.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { addDnaFeature(last_cursor_position, selection, entry_group, new Key("region")); } }); JMenuItem createFeatureProtein = new JMenuItem("polypeptide"); createFeatureProtein.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { if(selection.getAllFeatures().size() < 1) { JOptionPane.showMessageDialog(null, "Select a single transcript and try again.", "Transcript Selection", JOptionPane.ERROR_MESSAGE); return; } uk.ac.sanger.artemis.Feature transcript = selection.getAllFeatures().elementAt(0); final String transcriptName; try { transcriptName = (String)transcript.getQualifierByName("ID").getValues().get(0); } catch(InvalidRelationException e1) { e1.printStackTrace(); return; } if(!chado_gene.isTranscript(transcriptName)) { JOptionPane.showMessageDialog(null, "Select a single transcript and try again.", "Transcript Selection", JOptionPane.ERROR_MESSAGE); return; } addProteinFeature(chado_gene, entry_group, transcriptName, transcript); } }); createFeatureMenu.add(createFeatureProtein); if(!DatabaseDocument.CHADO_INFER_CDS) { createFeatureMenu.add(createFeature5Utr); createFeatureMenu.add(createFeature3Utr); } createFeatureMenu.add(createFeatureDna); menu.add(createFeatureMenu); menu.add(new JSeparator()); JMenuItem adjustCoords = new JMenuItem("Fix selected transcript coordinates"); adjustCoords.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { FeatureVector features = selection.getAllFeatures(); if(features.size() != 1) { JOptionPane.showMessageDialog(null, "Select a single transcript and try again.", "Transcript Selection", JOptionPane.ERROR_MESSAGE); return; } final uk.ac.sanger.artemis.Feature transcript = features.elementAt(0); GeneUtils.checkTranscriptBoundary(transcript, chado_gene); gene_builder.dispose(true); } }); menu.add(adjustCoords); JMenuItem adjustGeneCoords = new JMenuItem("Fix gene model coordinates"); adjustGeneCoords.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { GeneUtils.checkGeneBoundary(chado_gene); gene_builder.dispose(true); } }); menu.add(adjustGeneCoords); menu.add(new JSeparator()); final JMenuItem convertPsuedoMenu = new JMenuItem("Convert gene to/from pseudogene"); convertPsuedoMenu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { try { GeneUtils.convertPseudo(chado_gene); gene_builder.dispose(true); } catch(ReadOnlyException e) { e.printStackTrace(); } catch(EntryInformationException e) { e.printStackTrace(); } catch(OutOfRangeException e) { e.printStackTrace(); } } }); menu.add(convertPsuedoMenu); } /** * Delete or make features obsolete */ protected static Boolean deleteFeatures(FeatureVector features, ChadoCanonicalGene chado_gene) { final String feature_count_string; if (features.size () < 2) feature_count_string = "the selected feature"; else feature_count_string = features.size () + " features"; Box boption = Box.createVerticalBox(); JCheckBox delete = new JCheckBox("permanently delete", !Options.getOptions().getPropertyTruthValue("set_obsolete_on_delete")); boption.add(new JLabel("Make "+feature_count_string+" obsolete?")); boption.add(delete); int option = JOptionPane.showConfirmDialog(null, boption, "Make obsolete", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if(option == JOptionPane.CANCEL_OPTION) return null; return delete.isSelected(); } public static uk.ac.sanger.artemis.Feature createTranscript(final ChadoCanonicalGene chadoGene, final EntryGroup entry_group) { return createTranscript(chadoGene, entry_group, chadoGene.getGene().getLocation()); } public static uk.ac.sanger.artemis.Feature createTranscript(final ChadoCanonicalGene chadoGene, final EntryGroup entry_group, final Location location) { try { String gene_name = (String)chadoGene.getGene().getQualifierByName("ID").getValues().get(0); String ID = chadoGene.autoGenerateTanscriptName(DatabaseDocument.TRANSCRIPT); QualifierVector qualifiers = new QualifierVector(); qualifiers.add(new Qualifier("Parent", gene_name)); if(ID != null) qualifiers.add(new Qualifier("ID", ID)); final Key transcriptKey; if(chadoGene.getGene().getKey().getKeyString().equals("pseudogene")) transcriptKey = new Key("pseudogenic_transcript"); else transcriptKey = new Key(DatabaseDocument.TRANSCRIPT); uk.ac.sanger.artemis.Feature feature = createFeature( location, entry_group, transcriptKey, qualifiers); ((GFFStreamFeature)(feature.getEmblFeature())).setChadoGene(chadoGene); chadoGene.addTranscript(feature.getEmblFeature()); return feature; } catch(InvalidRelationException e) { e.printStackTrace(); } return null; } /** * */ public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; Feature embl_gene = (Feature)chado_gene.getGene(); uk.ac.sanger.artemis.Feature gene = (uk.ac.sanger.artemis.Feature)embl_gene.getUserData(); setFont(uk.ac.sanger.artemis.Options.getOptions().getFont()); start = embl_gene.getFirstBase(); int end = embl_gene.getLastBase(); g2d.setColor( gene.getColour() ); int ypos = border; // draw gene g2d.drawString(gene.getIDString()+ ( GeneUtils.isObsolete((GFFStreamFeature)embl_gene) ? " (obsolete)" : "") , border, ypos); drawFeature(g2d, border, getSize().width - border, ypos, gene.getColour(), 1, selection.contains(gene), 2.f, getFontHeight()); List transcripts = chado_gene.getTranscripts(); fraction = (float)(getSize().width - (2*border))/ (float)(end-start); ypos += border*2; for(int i=0; i= (border*3)+(getTranscriptSize()*i) && p.y <= (border*3)+(getTranscriptSize()*i)+getFontHeight()) { return (Feature)transcripts.get(i); } else if(p.y >= (border*3)+(getTranscriptSize()*i)+getFontHeight() && p.y <= (border*3)+(getTranscriptSize()*(i+1))) { Feature feature = (Feature)transcripts.get(i); String transcript_name = getQualifier(feature, "ID"); List splicedFeatures = chado_gene.getSplicedFeaturesOfTranscript(transcript_name); if(splicedFeatures != null) { for(int j = 0; j < splicedFeatures.size(); j++) { FeatureSegmentVector segments = ((uk.ac.sanger.artemis.Feature) ((Feature) splicedFeatures.get(j)).getUserData()).getSegments(); if(segments.size() == 0) return (Feature)splicedFeatures.get(j); for(int k = 0; k < segments.size(); k++) { FeatureSegment segment = segments.elementAt(k); Range segment_range = segment.getRawRange(); int segment_start = border + (int) ((segment_range.getStart() - start) * fraction); int segment_end = border + (int) ((segment_range.getEnd() - start) * fraction); if(p.x >= segment_start && p.x <= segment_end) return segment; } } } final List utr_3 = chado_gene.get3UtrOfTranscript(transcript_name); final List utr_5 = chado_gene.get5UtrOfTranscript(transcript_name); final List utrs = new Vector(); if(utr_3 != null) utrs.addAll(utr_3); if(utr_5 != null) utrs.addAll(utr_5); for(int j = 0; j < utrs.size(); j++) { Feature utr = (Feature) utrs.get(j); Range range = utr.getLocation().getTotalRange(); int utr_start = border + (int) ((range.getStart() - start) * fraction); int utr_end = border + (int) ((range.getEnd() - start) * fraction); if(p.x >= utr_start && p.x <= utr_end) return utr; } // anything else List others = chado_gene.getOtherFeaturesOfTranscript(transcript_name); if(others != null) { for(int j=0; j= r_start && p.x <= r_end) return other; } } } } return null; } /** * Get the Point for a given transcript name * @param transcriptName * @return */ private Point getPointFromTranscriptName(final String transcriptName) { final List transcripts = chado_gene.getTranscripts(); for(int i=0; iColor for a feature from its colour attribute. * @param feature * @return */ /*private Color getColorFromAttributes(org.gmod.schema.sequence.Feature feature) { List properties = new Vector(feature.getFeatureProps()); for(int i=0; i 0) range_selected = ((GFFStreamFeature)exons.get(0)).getLocation().getTotalRange(); else range_selected = transcript.getLocation().getTotalRange(); final String pepName = chadoGene.autoGeneratePepName(transcriptName); try { return addFeature(range_selected, transcriptName, pepName, transcript.getLocation().isComplement(), false, chadoGene, entry_group, new Key("polypeptide"), "pep"); } catch(OutOfRangeException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * Create an exons feature * @param feature * @param range * @param transcript_name */ public static GFFStreamFeature addExonFeature( final ChadoCanonicalGene chadoGene, final EntryGroup entry_group, final GFFStreamFeature feature, Range range, final String transcript_name, final Selection selection, final Key exonKey, final GeneBuilderFrame gene_builder) { try { if(feature == null) { QualifierVector qualifiers = new QualifierVector(); qualifiers.add(new Qualifier("Parent", transcript_name)); final String ID = chadoGene.autoGenerateSplicedFeatureName(transcript_name); if(ID != null) qualifiers.add(new Qualifier("ID", ID)); uk.ac.sanger.artemis.Feature exon = createFeature( new Location(new RangeVector(range), chadoGene.getGene().getLocation().isComplement()), entry_group, exonKey, qualifiers); GFFStreamFeature gff_exon = (GFFStreamFeature)exon.getEmblFeature(); chadoGene.addSplicedFeatures(transcript_name, gff_exon); ((GFFStreamFeature)(exon.getEmblFeature())).setChadoGene(chadoGene); if(ID != null) { final Hashtable id_range_store = new Hashtable(); id_range_store.put(ID, range); gff_exon.setSegmentRangeStore(id_range_store); } return gff_exon; } else { // add new ID final RangeVector ranges = new RangeVector(); ranges.add(range); GeneUtils.addSegment(feature, ranges, transcript_name); final QualifierVector old_qualifiers = feature.getQualifiers().copy(); ((uk.ac.sanger.artemis.Feature)feature.getUserData()).addSegment(range, old_qualifiers); if(gene_builder != null) gene_builder.setActiveFeature((uk.ac.sanger.artemis.Feature)feature.getUserData(), false); } } catch(InvalidRelationException e) { e.printStackTrace(); } catch(ReadOnlyException e) { e.printStackTrace(); } catch(EntryInformationException e) { // TODO Auto-generated catch block e.printStackTrace(); } return feature; } private static String getQualifier(Feature feature, String name) { Qualifier qualifier = null; try { qualifier = feature.getQualifierByName(name); } catch(InvalidRelationException e) { e.printStackTrace(); } if(qualifier == null) return null; return (String) (qualifier.getValues().get(0)); } /** * Popup listener */ class PopupListener extends MouseAdapter { public void mousePressed(MouseEvent e) { click_segment_marker = null; maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) maybeShowPopup(e); click_segment_marker = null; } private void maybeShowPopup(MouseEvent e) { if(e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } else if(e.getButton() != MouseEvent.BUTTON3 && e.getClickCount() == 1) { if(selection.getMarkerRange() != null && e.isShiftDown()) return; click_range = null; if(!e.isShiftDown()) selection.clear(); Object feature = getFeatureAt(e.getPoint()); if(feature == null) return; if(e.isShiftDown()) { if(feature instanceof Feature) selection.add( (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData()); else selection.add((FeatureSegment)feature); } else { if(feature instanceof Feature) { selection.set( (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData()); boolean isSet = true; if(((Feature)feature).isReadOnly()) isSet = false; gene_builder.setActiveFeature( (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData(), isSet); } else { selection.set((FeatureSegment)feature); gene_builder.setActiveFeature(((FeatureSegment)feature).getFeature(), true); } } // the following is used by mouseDragged() to edit feature locations if(feature instanceof Feature) clicked_feature = (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData(); else clicked_feature = ((FeatureSegment)feature).getFeature(); click_segment_marker = null; if(Options.getOptions().canDirectEdit() && !clicked_feature.isReadOnly() && clicked_feature.getEntry().getBases() != null) { // search the feature to find if the start Marker of any of the // segments matches the start Marker of new_click_range or if an end // Marker matches the end Marker of new_click_range final FeatureSegmentVector segments = clicked_feature.getSegments(); for(int k = 0; k < segments.size(); k++) { FeatureSegment this_segment = segments.elementAt(k); Range segment_range = this_segment.getRawRange(); int segment_start = border + (int) ((segment_range.getStart() - start) * fraction); int segment_end = border + (int) ((segment_range.getEnd() - start) * fraction); // allow very ends of features to be dragable if( (e.getPoint().x >= segment_start && e.getPoint().x < segment_start+3 && e.getPoint().x <= segment_end) || (e.getPoint().x <= segment_end && e.getPoint().x > segment_end-3 && e.getPoint().x >= segment_start) ) { int segment_mid = (segment_start+segment_end)/2; if((e.getPoint().x < segment_mid && this_segment.isForwardSegment()) || (e.getPoint().x > segment_mid && !this_segment.isForwardSegment())) { click_segment_marker = this_segment.getStart(); click_segment_marker_is_start_marker = true; other_end_of_segment_marker = this_segment.getEnd(); } else { click_segment_marker = this_segment.getEnd(); click_segment_marker_is_start_marker = false; other_end_of_segment_marker = this_segment.getStart(); } } } } repaint(); } } } }