From 0cb52d6f867579abede6e5c6a067084518d19b1b Mon Sep 17 00:00:00 2001 From: tjc <tjc@ee4ac58c-ac51-4696-9907-e4b3aa274f04> Date: Wed, 9 Jun 2004 09:48:01 +0000 Subject: [PATCH] Java2 version git-svn-id: svn+ssh://svn.internal.sanger.ac.uk/repos/svn/pathsoft/artemis/trunk@1635 ee4ac58c-ac51-4696-9907-e4b3aa274f04 --- uk/ac/sanger/artemis/components/ActMain.java | 283 + uk/ac/sanger/artemis/components/AddMenu.java | 1070 ++++ .../artemis/components/AlignMatchViewer.java | 366 ++ .../artemis/components/AlignmentEvent.java | 57 + .../artemis/components/AlignmentListener.java | 43 + .../artemis/components/AlignmentViewer.java | 1604 ++++++ .../components/ArtemisCheckboxMenuItem.java | 127 + .../artemis/components/ArtemisMain.java | 634 +++ uk/ac/sanger/artemis/components/BasePlot.java | 627 +++ .../artemis/components/BasePlotGroup.java | 337 ++ .../components/BioJavaEntrySource.java | 223 + .../artemis/components/CanvasPanel.java | 240 + .../artemis/components/ChoiceFrame.java | 111 + .../artemis/components/ComparatorDialog.java | 331 ++ .../artemis/components/ComparatorGlue.java | 464 ++ .../artemis/components/CorbaEntrySource.java | 90 + .../components/DbfetchEntrySource.java | 253 + .../components/DisplayAdjustmentEvent.java | 186 + .../components/DisplayAdjustmentListener.java | 45 + .../artemis/components/DisplayComponent.java | 52 + .../components/EMBLCorbaEntrySource.java | 318 ++ uk/ac/sanger/artemis/components/EditMenu.java | 2447 +++++++++ .../components/EntryActionListener.java | 63 + .../sanger/artemis/components/EntryEdit.java | 1434 +++++ .../artemis/components/EntryEditVector.java | 92 + .../artemis/components/EntryFileDialog.java | 463 ++ .../artemis/components/EntryGroupDisplay.java | 240 + .../components/EntryGroupInfoDisplay.java | 536 ++ .../artemis/components/EntryGroupMenu.java | 336 ++ .../artemis/components/EntryGroupPanel.java | 376 ++ .../artemis/components/EntryHeaderEdit.java | 209 + .../components/ExternalProgramOptions.java | 66 + .../components/FeatureAminoAcidViewer.java | 172 + .../artemis/components/FeatureBaseViewer.java | 166 + .../artemis/components/FeatureDisplay.java | 4699 +++++++++++++++++ .../artemis/components/FeatureEdit.java | 1276 +++++ .../artemis/components/FeatureInfo.java | 658 +++ .../artemis/components/FeatureList.java | 971 ++++ .../artemis/components/FeatureListFrame.java | 195 + .../artemis/components/FeaturePlot.java | 389 ++ .../artemis/components/FeaturePlotGroup.java | 337 ++ .../artemis/components/FeaturePopup.java | 758 +++ .../artemis/components/FeatureViewer.java | 151 + .../components/FileDialogEntrySource.java | 286 + .../artemis/components/FileManager.java | 401 ++ uk/ac/sanger/artemis/components/FileNode.java | 159 + uk/ac/sanger/artemis/components/FileTree.java | 941 ++++ .../sanger/artemis/components/FileViewer.java | 293 + uk/ac/sanger/artemis/components/GotoMenu.java | 510 ++ .../sanger/artemis/components/GraphMenu.java | 403 ++ .../components/InputStreamProgressDialog.java | 126 + .../sanger/artemis/components/KeyChoice.java | 203 + .../sanger/artemis/components/KeyChooser.java | 118 + .../sanger/artemis/components/ListDialog.java | 128 + .../artemis/components/LogReadListener.java | 76 + .../sanger/artemis/components/LogViewer.java | 102 + .../components/MarkerRangeRequester.java | 106 + .../components/MarkerRangeRequesterEvent.java | 199 + .../MarkerRangeRequesterListener.java | 42 + .../artemis/components/MessageDialog.java | 143 + .../artemis/components/MessageFrame.java | 106 + .../artemis/components/MultiComparator.java | 1091 ++++ .../sanger/artemis/components/Navigator.java | 933 ++++ uk/ac/sanger/artemis/components/Plot.java | 723 +++ .../artemis/components/PlotMouseListener.java | 64 + .../artemis/components/ProcessWatcher.java | 194 + .../components/ProcessWatcherEvent.java | 70 + .../components/ProcessWatcherListener.java | 42 + .../artemis/components/ProgressThread.java | 64 + .../artemis/components/QualifierChoice.java | 181 + .../artemis/components/QualifierEditor.java | 332 ++ .../artemis/components/QualifierTextArea.java | 96 + uk/ac/sanger/artemis/components/RunMenu.java | 278 + .../artemis/components/ScoreChangeEvent.java | 58 + .../components/ScoreChangeListener.java | 40 + .../artemis/components/ScoreChanger.java | 222 + .../artemis/components/ScoreScrollbar.java | 120 + .../components/SearchResultViewer.java | 108 + .../sanger/artemis/components/SelectMenu.java | 827 +++ .../components/SelectionInfoDisplay.java | 435 ++ .../artemis/components/SelectionMenu.java | 294 ++ .../artemis/components/SelectionViewer.java | 352 ++ uk/ac/sanger/artemis/components/Selector.java | 885 ++++ .../artemis/components/SequenceViewer.java | 165 + uk/ac/sanger/artemis/components/Splash.java | 526 ++ .../artemis/components/StickyFileChooser.java | 99 + .../artemis/components/SwingWorker.java | 150 + .../artemis/components/TaskViewerFrame.java | 88 + .../sanger/artemis/components/TextDialog.java | 119 + .../artemis/components/TextFieldSink.java | 142 + .../artemis/components/TextRequester.java | 158 + .../components/TextRequesterEvent.java | 87 + .../components/TextRequesterListener.java | 44 + .../sanger/artemis/components/Utilities.java | 144 + uk/ac/sanger/artemis/components/ViewMenu.java | 1430 +++++ .../WritableEMBLCorbaEntrySource.java | 226 + .../sanger/artemis/components/WriteMenu.java | 852 +++ .../artemis/components/YesNoDialog.java | 115 + 98 files changed, 39591 insertions(+) create mode 100644 uk/ac/sanger/artemis/components/ActMain.java create mode 100644 uk/ac/sanger/artemis/components/AddMenu.java create mode 100644 uk/ac/sanger/artemis/components/AlignMatchViewer.java create mode 100644 uk/ac/sanger/artemis/components/AlignmentEvent.java create mode 100644 uk/ac/sanger/artemis/components/AlignmentListener.java create mode 100644 uk/ac/sanger/artemis/components/AlignmentViewer.java create mode 100644 uk/ac/sanger/artemis/components/ArtemisCheckboxMenuItem.java create mode 100644 uk/ac/sanger/artemis/components/ArtemisMain.java create mode 100644 uk/ac/sanger/artemis/components/BasePlot.java create mode 100644 uk/ac/sanger/artemis/components/BasePlotGroup.java create mode 100644 uk/ac/sanger/artemis/components/BioJavaEntrySource.java create mode 100644 uk/ac/sanger/artemis/components/CanvasPanel.java create mode 100644 uk/ac/sanger/artemis/components/ChoiceFrame.java create mode 100644 uk/ac/sanger/artemis/components/ComparatorDialog.java create mode 100644 uk/ac/sanger/artemis/components/ComparatorGlue.java create mode 100644 uk/ac/sanger/artemis/components/CorbaEntrySource.java create mode 100644 uk/ac/sanger/artemis/components/DbfetchEntrySource.java create mode 100644 uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java create mode 100644 uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java create mode 100644 uk/ac/sanger/artemis/components/DisplayComponent.java create mode 100644 uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java create mode 100644 uk/ac/sanger/artemis/components/EditMenu.java create mode 100644 uk/ac/sanger/artemis/components/EntryActionListener.java create mode 100644 uk/ac/sanger/artemis/components/EntryEdit.java create mode 100644 uk/ac/sanger/artemis/components/EntryEditVector.java create mode 100644 uk/ac/sanger/artemis/components/EntryFileDialog.java create mode 100644 uk/ac/sanger/artemis/components/EntryGroupDisplay.java create mode 100644 uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java create mode 100644 uk/ac/sanger/artemis/components/EntryGroupMenu.java create mode 100644 uk/ac/sanger/artemis/components/EntryGroupPanel.java create mode 100644 uk/ac/sanger/artemis/components/EntryHeaderEdit.java create mode 100644 uk/ac/sanger/artemis/components/ExternalProgramOptions.java create mode 100644 uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java create mode 100644 uk/ac/sanger/artemis/components/FeatureBaseViewer.java create mode 100644 uk/ac/sanger/artemis/components/FeatureDisplay.java create mode 100644 uk/ac/sanger/artemis/components/FeatureEdit.java create mode 100644 uk/ac/sanger/artemis/components/FeatureInfo.java create mode 100644 uk/ac/sanger/artemis/components/FeatureList.java create mode 100644 uk/ac/sanger/artemis/components/FeatureListFrame.java create mode 100644 uk/ac/sanger/artemis/components/FeaturePlot.java create mode 100644 uk/ac/sanger/artemis/components/FeaturePlotGroup.java create mode 100644 uk/ac/sanger/artemis/components/FeaturePopup.java create mode 100644 uk/ac/sanger/artemis/components/FeatureViewer.java create mode 100644 uk/ac/sanger/artemis/components/FileDialogEntrySource.java create mode 100644 uk/ac/sanger/artemis/components/FileManager.java create mode 100644 uk/ac/sanger/artemis/components/FileNode.java create mode 100644 uk/ac/sanger/artemis/components/FileTree.java create mode 100644 uk/ac/sanger/artemis/components/FileViewer.java create mode 100644 uk/ac/sanger/artemis/components/GotoMenu.java create mode 100644 uk/ac/sanger/artemis/components/GraphMenu.java create mode 100644 uk/ac/sanger/artemis/components/InputStreamProgressDialog.java create mode 100644 uk/ac/sanger/artemis/components/KeyChoice.java create mode 100644 uk/ac/sanger/artemis/components/KeyChooser.java create mode 100644 uk/ac/sanger/artemis/components/ListDialog.java create mode 100644 uk/ac/sanger/artemis/components/LogReadListener.java create mode 100644 uk/ac/sanger/artemis/components/LogViewer.java create mode 100644 uk/ac/sanger/artemis/components/MarkerRangeRequester.java create mode 100644 uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java create mode 100644 uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java create mode 100644 uk/ac/sanger/artemis/components/MessageDialog.java create mode 100644 uk/ac/sanger/artemis/components/MessageFrame.java create mode 100644 uk/ac/sanger/artemis/components/MultiComparator.java create mode 100644 uk/ac/sanger/artemis/components/Navigator.java create mode 100644 uk/ac/sanger/artemis/components/Plot.java create mode 100644 uk/ac/sanger/artemis/components/PlotMouseListener.java create mode 100644 uk/ac/sanger/artemis/components/ProcessWatcher.java create mode 100644 uk/ac/sanger/artemis/components/ProcessWatcherEvent.java create mode 100644 uk/ac/sanger/artemis/components/ProcessWatcherListener.java create mode 100644 uk/ac/sanger/artemis/components/ProgressThread.java create mode 100644 uk/ac/sanger/artemis/components/QualifierChoice.java create mode 100644 uk/ac/sanger/artemis/components/QualifierEditor.java create mode 100644 uk/ac/sanger/artemis/components/QualifierTextArea.java create mode 100644 uk/ac/sanger/artemis/components/RunMenu.java create mode 100644 uk/ac/sanger/artemis/components/ScoreChangeEvent.java create mode 100644 uk/ac/sanger/artemis/components/ScoreChangeListener.java create mode 100644 uk/ac/sanger/artemis/components/ScoreChanger.java create mode 100644 uk/ac/sanger/artemis/components/ScoreScrollbar.java create mode 100644 uk/ac/sanger/artemis/components/SearchResultViewer.java create mode 100644 uk/ac/sanger/artemis/components/SelectMenu.java create mode 100644 uk/ac/sanger/artemis/components/SelectionInfoDisplay.java create mode 100644 uk/ac/sanger/artemis/components/SelectionMenu.java create mode 100644 uk/ac/sanger/artemis/components/SelectionViewer.java create mode 100644 uk/ac/sanger/artemis/components/Selector.java create mode 100644 uk/ac/sanger/artemis/components/SequenceViewer.java create mode 100644 uk/ac/sanger/artemis/components/Splash.java create mode 100644 uk/ac/sanger/artemis/components/StickyFileChooser.java create mode 100644 uk/ac/sanger/artemis/components/SwingWorker.java create mode 100644 uk/ac/sanger/artemis/components/TaskViewerFrame.java create mode 100644 uk/ac/sanger/artemis/components/TextDialog.java create mode 100644 uk/ac/sanger/artemis/components/TextFieldSink.java create mode 100644 uk/ac/sanger/artemis/components/TextRequester.java create mode 100644 uk/ac/sanger/artemis/components/TextRequesterEvent.java create mode 100644 uk/ac/sanger/artemis/components/TextRequesterListener.java create mode 100644 uk/ac/sanger/artemis/components/Utilities.java create mode 100644 uk/ac/sanger/artemis/components/ViewMenu.java create mode 100644 uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java create mode 100644 uk/ac/sanger/artemis/components/WriteMenu.java create mode 100644 uk/ac/sanger/artemis/components/YesNoDialog.java diff --git a/uk/ac/sanger/artemis/components/ActMain.java b/uk/ac/sanger/artemis/components/ActMain.java new file mode 100644 index 000000000..486d2a4a6 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ActMain.java @@ -0,0 +1,283 @@ +/* ActMain.java + * + * created: Wed May 10 2000 + * + * This file is part of Artemis + * + * Copyright(C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or(at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ActMain.java,v 1.1 2004-06-09 09:45:56 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.Bases; + +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; + +import java.awt.event.*; +import java.io.IOException; +import javax.swing.JFrame; + +/** + * The main window for the Artemis Comparison Tool. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ActMain.java,v 1.1 2004-06-09 09:45:56 tjc Exp $ + **/ + +public class ActMain extends Splash +{ + /** Version String use for banner messages and title bars. */ + public static final String version = "Release 2"; + /** File manager */ + protected static FileManager filemanager = null; + + /** + * The constructor creates all the components for the main ACT window + * and sets up all the menu callbacks. + **/ + public ActMain() + { + super("Artemis Comparison Tool", "ACT", version); + + ActionListener open_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + makeOpenDialog(); + } + }; + + makeMenuItem(file_menu, "Open ...", open_listener); + + ActionListener quit_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + exit(); + } + }; + + makeMenuItem(file_menu, "Quit", quit_listener); + } + + /** + * Make a new Comparator component from the given files. + * @param frame The JFrame used when making a new MessageDialog. + * @param progress_listener The object to which InputStreamProgressEvents + * will be send while reading. Can be null. + * @param file_names Alternating sequence and comparison data file names. + * Must be >= 3. I there are an even number of file names the first + * file/sequence object will be added to the send of the display and the + * last comparison file will be assumed to be a a comparison between the + * last and first sequence files. + **/ + public static boolean makeMultiComparator(final JFrame frame, + final InputStreamProgressListener progress_listener, + final String[] file_names) + { + + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + + SwingWorker entryWorker = new SwingWorker() + { + public Object construct() + { + progress_thread.start(); + final EntryGroup[] entry_group_array = + new EntryGroup[file_names.length / 2 + 1]; + + final ComparisonData[] comparison_data_array = + new ComparisonData[file_names.length / 2]; + + for(int i = 0; i<file_names.length; i += 2) + { + final EntryInformation entry_information = + new SimpleEntryInformation(Options.getArtemisEntryInformation()); + + final String this_file_name = file_names[i]; + + final Document entry_document = + DocumentFactory.makeDocument(this_file_name); + + if(progress_listener != null) + entry_document.addInputStreamProgressListener(progress_listener); + + final uk.ac.sanger.artemis.io.Entry embl_entry = + EntryFileDialog.getEntryFromFile(frame, entry_document, + entry_information, + true); + + // getEntryFromFile() has alerted the user so we just need to quit + if(embl_entry == null) + return null; + + final uk.ac.sanger.artemis.io.Sequence sequence = + embl_entry.getSequence(); + + if(sequence == null) + { + new MessageDialog(frame, "This file contains no sequence: " + + this_file_name); + return null; + } + + final Bases embl_bases = new Bases(sequence); + final EntryGroup entry_group = new SimpleEntryGroup(embl_bases); + + try + { + final Entry entry = new Entry(entry_group.getBases(), embl_entry); + entry_group.add(entry); + entry_group_array[i / 2] = entry_group; + } + catch(OutOfRangeException e) + { + new MessageDialog(frame, "read failed: one of the features has an " + + "out of range location: " + e.getMessage()); + return null; + } + } + + // add the first entry at the end to make the MultiComparator + // circular(-ish) + if(file_names.length % 2 == 0) + entry_group_array[entry_group_array.length - 1] = entry_group_array[0]; + + try + { + for(int i = 1 ; i < file_names.length ; i += 2) + { + final String comparison_data_file_name = file_names[i]; + final Document comparison_data_document = + DocumentFactory.makeDocument(comparison_data_file_name); + + comparison_data_array[i / 2] = + ComparisonDataFactory.readComparisonData(comparison_data_document); + + final Bases prev_bases = entry_group_array[i/2].getBases(); + final Bases next_bases = entry_group_array[i/2 + 1].getBases(); + + final ComparisonData swapped_comparison_data = + comparison_data_array[i / 2].flipMatchesIfNeeded(prev_bases, + next_bases); + + if(swapped_comparison_data != null) + comparison_data_array[i / 2] = swapped_comparison_data; + + if(swapped_comparison_data != null) + { + final MessageFrame message_frame = + new MessageFrame("note: hits from " + comparison_data_file_name + + " have been flipped to match the " + + "sequences"); + + message_frame.setVisible(true); + } + } + } + catch(IOException e) + { + new MessageDialog(frame, "error while reading: " + e.getMessage()); + return null; + } + catch(OutOfRangeException e) + { + new MessageDialog(frame, "comparison file read failed: " + + "out of range error: " + e.getMessage()); + return null; + } + + final MultiComparator comparator = + new MultiComparator(entry_group_array, + comparison_data_array, + progress_listener); + + comparator.setVisible(true); + return null; + } + + public void finished() + { + if(progress_thread !=null) + progress_thread.finished(); + } + + }; + entryWorker.start(); + + return true; + } + + /** + * Create a dialog that allow the user to the choose two files to compare + * and a file containing comparison data. + **/ + private void makeOpenDialog() + { + if(filemanager == null) + filemanager = new FileManager(this,null); + else + filemanager.setVisible(true); + new ComparatorDialog(this).setVisible(true); + } + + /** + * Exit from ACT. + **/ + protected void exit() + { + System.exit(0); + } + + /** + * Main entry point for ACT + **/ + public static void main(final String [] args) + { + final ActMain main_window = new ActMain(); + main_window.setVisible(true); + + final InputStreamProgressListener progress_listener = + main_window.getInputStreamProgressListener(); + + if(args.length >= 3) + ActMain.makeMultiComparator(main_window, progress_listener, + args); + else + { + if(args.length != 0) + { + System.err.println("Error - this program needs either no " + + " arguments or an odd number\n" + + "(3 or more):"); + System.err.println(" act sequence_1 comparison_data sequence_2"); + System.err.println("or"); + System.err.println(" act seq_1 comparison_data_2_v_1 seq_2 comparison_data_3_v_2 seq_3"); + System.err.println("or"); + System.err.println(" act"); + System.exit(1); + } + } + } + +} diff --git a/uk/ac/sanger/artemis/components/AddMenu.java b/uk/ac/sanger/artemis/components/AddMenu.java new file mode 100644 index 000000000..c432f8748 --- /dev/null +++ b/uk/ac/sanger/artemis/components/AddMenu.java @@ -0,0 +1,1070 @@ +/* AddMenu.java + * + * created: Tue Dec 29 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000,2001,2002 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/AddMenu.java,v 1.1 2004-06-09 09:45:57 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.plot.CodonUsageAlgorithm; + +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.Range; +import uk.ac.sanger.artemis.io.RangeVector; +import uk.ac.sanger.artemis.io.Location; +import uk.ac.sanger.artemis.io.QualifierVector; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.QualifierParseException; +import uk.ac.sanger.artemis.io.InvalidQualifierException; +import uk.ac.sanger.artemis.io.InvalidRelationException; +import uk.ac.sanger.artemis.io.InvalidKeyException; +import uk.ac.sanger.artemis.io.LocationParseException; +import uk.ac.sanger.artemis.io.EntryInformationException; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A Menu with commands that add new features/entries to an EntryGroup. This + * should have been called CreateMenu. + * + * @author Kim Rutherford + * @version $Id: AddMenu.java,v 1.1 2004-06-09 09:45:57 tjc Exp $ + **/ + +public class AddMenu extends SelectionMenu { + /** + * The shortcut for "Create From Base Range". + **/ + final static KeyStroke CREATE_FROM_BASE_RANGE_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_C, InputEvent.CTRL_MASK); + + final static public int CREATE_FROM_BASE_RANGE_KEY_CODE = KeyEvent.VK_C; + + /** + * Create a new AddMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group The EntryGroup object where new features/entries will + * be added. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + * @param menu_name The name of the new menu. + **/ + public AddMenu (final JFrame frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_event_source, + final BasePlotGroup base_plot_group, + final String menu_name) { + super (frame, menu_name, selection); + + this.entry_group = entry_group; + this.base_plot_group = base_plot_group; + + new_feature_item = new JMenuItem ("New Feature"); + new_feature_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + makeNewFeature (); + } + }); + + add (new_feature_item); + + create_feature_from_range_item = + new JMenuItem ("Create Feature From Base Range"); + create_feature_from_range_item.setAccelerator (CREATE_FROM_BASE_RANGE_KEY); + create_feature_from_range_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + createFeatureFromBaseRange (getParentFrame (), getSelection (), + entry_group, getGotoEventSource ()); + } + }); + + add (create_feature_from_range_item); + + create_intron_features_item = + new JMenuItem ("Create Intron Features"); + create_intron_features_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + createIntronFeatures (getParentFrame (), getSelection (), + entry_group); + } + }); + + add (create_intron_features_item); + + create_exon_features_item = + new JMenuItem ("Create Exon Features"); + create_exon_features_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + createExonFeatures (getParentFrame (), getSelection (), + entry_group); + } + }); + + add (create_exon_features_item); + + create_gene_features_item = + new JMenuItem ("Create Gene Features"); + create_gene_features_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + createGeneFeatures (getParentFrame (), getSelection (), + entry_group); + } + }); + + add (create_gene_features_item); + + addSeparator (); + + new_entry_item = new JMenuItem ("New Entry"); + new_entry_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + makeNewEntry (); + } + }); + + add (new_entry_item); + + addSeparator (); + + mark_orfs_with_size_item = new JMenuItem ("Mark Open Reading Frames ..."); + + mark_orfs_with_size_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + markORFSWithSize (false); + } + }); + + add (mark_orfs_with_size_item); + + + mark_empty_orfs_with_size_item = new JMenuItem ("Mark Empty ORFs ..."); + + mark_empty_orfs_with_size_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + markORFSWithSize (true); + } + }); + + add (mark_empty_orfs_with_size_item); + + + mark_orfs_range_item = new JMenuItem ("Mark ORFs In Range ..."); + mark_orfs_range_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + markOpenReadingFramesInRange (); + } + }); + + add (mark_orfs_range_item); + + + mark_pattern_item = new JMenuItem ("Mark From Pattern ..."); + mark_pattern_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + makeFeaturesFromPattern (); + } + }); + + add (mark_pattern_item); + + mark_ambiguities_item = new JMenuItem ("Mark Ambiguities"); + mark_ambiguities_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + markAmbiguities (); + } + }); + + add (mark_ambiguities_item); + } + + /** + * Create a new AddMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group The EntryGroup object where new features/entries will + * be added. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public AddMenu (final JFrame frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_event_source, + final BasePlotGroup base_plot_group) { + this (frame, selection, entry_group, + goto_event_source, base_plot_group, "Create"); + } + + /** + * Create a new feature in the default Entry of the entry group. See + * EntryGroup.createFeature () for details. + **/ + private void makeNewFeature () { + if (entry_group.size () > 0) { + if (entry_group.getDefaultEntry () == null) { + new MessageDialog (getParentFrame (), "There is no default entry"); + } else { + + try { + entry_group.getActionController ().startAction (); + + final Feature new_feature = entry_group.createFeature (); + + final FeatureEdit feature_edit = + new FeatureEdit (new_feature, entry_group, getSelection (), + getGotoEventSource ()); + + final ActionListener cancel_listener = + new ActionListener () { + public void actionPerformed (ActionEvent e) { + try { + new_feature.removeFromEntry (); + } catch (ReadOnlyException exception) { + throw new Error ("internal error - unexpected exception: " + + exception); + } + } + }; + + feature_edit.addCancelActionListener (cancel_listener); + + feature_edit.addApplyActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + // after apply is pressed cancel should not remove the new + // feature + feature_edit.removeCancelActionListener (cancel_listener); + } + }); + + feature_edit.show (); + } catch (ReadOnlyException e) { + new MessageDialog (getParentFrame (), "feature not created: " + + "the default entry is read only"); + } finally { + entry_group.getActionController ().endAction (); + } + } + } else { + new MessageDialog (getParentFrame (), + "Cannot make a feature without an existing entry"); + } + } + + /** + * Create a new Entry in the first Entry of the entry group. + **/ + private void makeNewEntry () { + entry_group.createEntry (); + } + + /** + * Create a new Feature in entry_group from the selected range of bases and + * then display a FeatureEdit component for the new Feature. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the sequence to create the + * feature from. + * @param entry_group The EntryGroup to create the feature in. + * @param goto_event_source Needed to create a FeatureEdit component. + **/ + static void createFeatureFromBaseRange (final JFrame frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource + goto_event_source) { + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionRange (frame, selection)) { + return; + } + + final MarkerRange range = selection.getMarkerRange (); + final Entry default_entry = entry_group.getDefaultEntry (); + + if (default_entry == null) { + new MessageDialog (frame, "There is no default entry"); + return; + } + + try { + final Location new_location = range.createLocation (); + + /*final*/ Feature temp_feature; + + try { + temp_feature = default_entry.createFeature (Key.CDS, new_location); + } catch (EntryInformationException e) { + // use the default key instead + + final Key default_key = + default_entry.getEntryInformation ().getDefaultKey (); + + try { + temp_feature = + default_entry.createFeature (default_key, new_location); + } catch (EntryInformationException ex) { + throw new Error ("internal error - unexpected exception: " + ex); + } + } + + final Feature new_feature = temp_feature; + + selection.setMarkerRange (null); + selection.set (new_feature); + + final FeatureEdit feature_edit = + new FeatureEdit (new_feature, entry_group, + selection, goto_event_source); + + final ActionListener cancel_listener = + new ActionListener () { + public void actionPerformed (ActionEvent e) { + try { + new_feature.removeFromEntry (); + selection.setMarkerRange (range); + } catch (ReadOnlyException exception) { + throw new Error ("internal error - unexpected exception: " + + exception); + } + } + }; + + feature_edit.addCancelActionListener (cancel_listener); + + feature_edit.addApplyActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + // after apply is pressed cancel should not remove the new + // feature + feature_edit.removeCancelActionListener (cancel_listener); + } + }); + + feature_edit.show (); + } catch (ReadOnlyException e) { + new MessageDialog (frame, "feature not created: " + + "the default entry is read only"); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Create a new intron between each pair of exons in the selected CDS + * features. The introns are created in the Entry that contains the CDSs. + * @param frame The Frame to use for MessageDialog components. + * @param selection The Selection containing the CDS features to create the + * introns for. + * @param entry_group The EntryGroup to create the features in. + **/ + static void createIntronFeatures (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionFeatures (frame, selection)) { + return; + } + + final FeatureVector selected_features = selection.getAllFeatures (); + + for (int feature_index = 0 ; + feature_index < selected_features.size () ; + ++feature_index) { + + final Feature selection_feature = + selected_features.elementAt (feature_index); + + if (!selection_feature.isProteinFeature ()) { + continue; + } + + final Location cds_location = selection_feature.getLocation (); + + final RangeVector cds_ranges = cds_location.getRanges (); + + if (cds_ranges.size () < 2) { + continue; + } + + if (cds_location.isComplement ()) { + cds_ranges.reverse (); + } + + for (int range_index = 0 ; + range_index < cds_ranges.size () - 1 ; + ++range_index) { + final int end_of_range_1 = + cds_ranges.elementAt (range_index).getEnd (); + final int start_of_range_2 = + cds_ranges.elementAt (range_index + 1).getStart (); + + if (end_of_range_1 > start_of_range_2) { + // ignore - the exons overlap so there is no room for an intron + continue; + } + + final Range new_range; + + try { + new_range = new Range (end_of_range_1 + 1, + start_of_range_2 - 1); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + final RangeVector intron_ranges = new RangeVector (); + + intron_ranges.add (new_range); + + final Key intron_key = new Key ("intron"); + final Location intron_location = + new Location (intron_ranges, cds_location.isComplement ()); + final QualifierVector qualifiers = new QualifierVector (); + + try { + selection_feature.getEntry ().createFeature (intron_key, + intron_location, + qualifiers); + } catch (ReadOnlyException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (EntryInformationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Create a new exon for each FeatureSegment in the selected CDS features. + * The exons are created in the Entry that contains the CDSs. + * @param frame The Frame to use for MessageDialog components. + * @param selection The Selection containing the CDS features to create the + * exons for. + * @param entry_group The EntryGroup to create the features in. + **/ + static void createExonFeatures (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionFeatures (frame, selection)) { + return; + } + + final FeatureVector selected_features = selection.getAllFeatures (); + + for (int feature_index = 0 ; + feature_index < selected_features.size () ; + ++feature_index) { + + final Feature selection_feature = + selected_features.elementAt (feature_index); + + if (!selection_feature.isProteinFeature ()) { + continue; + } + + final Location cds_location = selection_feature.getLocation (); + + final RangeVector cds_ranges = cds_location.getRanges (); + + for (int range_index = 0 ; + range_index < cds_ranges.size () ; + ++range_index) { + final Range this_range = cds_ranges.elementAt (range_index); + + final RangeVector exon_ranges = new RangeVector (); + + exon_ranges.add (this_range); + + final Key exon_key = new Key ("exon"); + final Location exon_location = + new Location (exon_ranges, cds_location.isComplement ()); + final QualifierVector qualifiers = new QualifierVector (); + + try { + selection_feature.getEntry ().createFeature (exon_key, + exon_location, + qualifiers); + } catch (ReadOnlyException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (EntryInformationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Create a new gene for each of the selected CDS features. + * @param frame The Frame to use for MessageDialog components. + * @param selection The Selection containing the CDS features. + * @param entry_group The EntryGroup to create the features in. + **/ + static void createGeneFeatures (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + /* + * XXX - FIXME - include 5'UTR at the start and 3'UTR at the end and if + * two (or more) CDSs have the same primary name create one gene feature + * that covers them. + */ + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionFeatures (frame, selection)) { + return; + } + + final FeatureVector selected_features = selection.getAllFeatures (); + + for (int feature_index = 0 ; + feature_index < selected_features.size () ; + ++feature_index) { + + final Feature selection_feature = + selected_features.elementAt (feature_index); + + if (!selection_feature.isProteinFeature ()) { + continue; + } + + final Range max_range = selection_feature.getMaxRawRange (); + final boolean complement_flag = + selection_feature.getLocation ().isComplement (); + + final RangeVector ranges = new RangeVector (); + ranges.add (max_range); + + final Key gene_key = new Key ("gene"); + final Location gene_location = + new Location (ranges, complement_flag); + final QualifierVector qualifiers = new QualifierVector (); + + try { + selection_feature.getEntry ().createFeature (gene_key, + gene_location, + qualifiers); + } catch (ReadOnlyException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (EntryInformationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Open a TextRequester to ask the user for the minimum ORF size then call + * markOpenReadingFrames (). + * @param empty_only If true only those ORFS that don't already contain a + * segment will be marked. + **/ + private void markORFSWithSize (final boolean empty_only) { + final int default_minimum_orf_size = + Options.getOptions ().getMinimumORFSize (); + + final TextRequester text_requester = + new TextRequester ("minimum open reading frame size?", + 18, String.valueOf (default_minimum_orf_size)); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + if (event.getType () == TextRequesterEvent.CANCEL) { + return; + } + + final String requester_text = event.getRequesterText ().trim (); + + if (requester_text.length () == 0) { + return; + } + + try { + final int minimum_orf_size = + Integer.valueOf (requester_text).intValue (); + + markOpenReadingFrames (minimum_orf_size, empty_only); + } catch (NumberFormatException e) { + new MessageDialog (getParentFrame (), + "this is not a number: " + requester_text); + } + } + }); + + text_requester.setVisible (true); + } + + /** + * Create a new Feature for each open reading frame. + * @param minimum_orf_size All the returned ORFs will be at least this many + * amino acids long. + * @param empty_only If true only those ORFS that don't already contain a + * segment will be marked. + **/ + private void markOpenReadingFrames (final int minimum_orf_size, + final boolean empty_only) { + try { + final Entry new_entry = + entry_group.createEntry ("ORFS_" + minimum_orf_size + '+'); + + final int sequence_length = entry_group.getSequenceLength (); + + final Strand forward_strand = + entry_group.getBases ().getForwardStrand (); + + final MarkerRange forward_range = + forward_strand.makeMarkerRangeFromPositions (1, sequence_length); + + markOpenReadingFrames (new_entry, forward_range, minimum_orf_size, + empty_only); + + final Strand backward_strand = + entry_group.getBases ().getReverseStrand (); + + final MarkerRange backward_range = + backward_strand.makeMarkerRangeFromPositions (1, sequence_length); + + markOpenReadingFrames (new_entry, backward_range, minimum_orf_size, + empty_only); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected OutOfRangeException"); + } + } + + /** + * Create a new Feature for each open reading frame. The minimum size of + * the ORFS is specified in the options file. + **/ + private void markOpenReadingFramesInRange () { + if (!checkForSelectionRange (getParentFrame (), getSelection ())) { + return; + } + + final int default_minimum_orf_size = + Options.getOptions ().getMinimumORFSize (); + + final TextRequester text_requester = + new TextRequester ("minimum open reading frame size?", + 18, String.valueOf (default_minimum_orf_size)); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + if (event.getType () == TextRequesterEvent.CANCEL) { + return; + } + + final String requester_text = event.getRequesterText ().trim (); + + if (requester_text.length () == 0) { + return; + } + + try { + final int minimum_orf_size = + Integer.valueOf (requester_text).intValue (); + + final Entry new_entry = + entry_group.createEntry ("ORFS_" + minimum_orf_size + '+'); + + final MarkerRange selection_range = + getSelection ().getMarkerRange (); + + markOpenReadingFrames (new_entry, selection_range, minimum_orf_size, + false); + + + } catch (NumberFormatException e) { + new MessageDialog (getParentFrame (), + "this is not a number: " + requester_text); + } + } + }); + + text_requester.setVisible (true); + + } + + /** + * Create a new Feature in the given Entry for each open reading frame that + * overlaps the given range. The minimum size of the ORFS is specified in + * the options file. + * @param entry The new features are created in this entry. + * @param search_range The range of bases to search for ORFs. + * @param minimum_orf_size All the returned ORFs will be at least this many + * amino acids long. + * @param empty_only If true only those ORFS that don't already contain a + * segment will be marked. + **/ + private void markOpenReadingFrames (final Entry entry, + final MarkerRange search_range, + final int minimum_orf_size, + final boolean empty_only) { + final MarkerRange [] forward_orf_ranges = + Strand.getOpenReadingFrameRanges (search_range, minimum_orf_size); + + for (int i = 0 ; i < forward_orf_ranges.length ; ++i) { + final MarkerRange this_range = forward_orf_ranges[i]; + + final Feature new_feature; + + try { + new_feature = makeFeatureFromMarkerRange (entry, this_range, Key.CDS); + } catch (EntryInformationException e) { + new MessageDialog (getParentFrame (), "cannot continue: " + + "the default entry does not support CDS features"); + return; + } catch (ReadOnlyException e) { + new MessageDialog (getParentFrame (), "cannot continue: " + + "the default entry is read only"); + return; + } + + if (empty_only && overlapsAnActiveSegment (new_feature)) { + try { + new_feature.removeFromEntry (); + } catch (ReadOnlyException exception) { + throw new Error ("internal error - unexpected exception: " + + exception); + } + } + } + } + + /** + * Return true if and only if the given feature overlaps (and is in the + * same frame as) a segment in an active entry. + **/ + private boolean overlapsAnActiveSegment (final Feature test_feature) { + final Range test_feature_range = test_feature.getMaxRawRange (); + + FeatureVector overlapping_features; + + try { + overlapping_features = + entry_group.getFeaturesInRange (test_feature_range); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + for (int feature_index = 0 ; + feature_index < overlapping_features.size () ; + ++feature_index ) { + + final Feature current_feature = + overlapping_features.elementAt (feature_index); + + if (current_feature != test_feature && current_feature.isCDS ()) { + final FeatureSegmentVector segments = current_feature.getSegments (); + + for (int segment_index = 0; + segment_index < segments.size () ; + ++segment_index) { + final FeatureSegment this_segment = + segments.elementAt (segment_index); + + if (test_feature_range.overlaps (this_segment.getRawRange ())) { + final int test_feature_frame = + test_feature.getSegments ().elementAt (0).getFrameID (); + final int this_segment_frame = this_segment.getFrameID (); + + if (test_feature_frame == this_segment_frame) { + return true; + } + } + } + } + } + + return false; + } + + /** + * Make a new Feature from the given MarkerRange in the given Entry. The + * new feature will be given the key 'CDS' and it's location will match the + * MarkerRange. + * @param entry The new feature is created in this entry. + * @param range The location of the new feature. + * @param key The key give the new feature + * @exception EntryInformationException Thrown if this Entry does not + * support features with the given key. Also thrown if any of these + * qualifiers aren't supported: note, label or gene. + **/ + private Feature makeFeatureFromMarkerRange (final Entry entry, + final MarkerRange range, + final Key key) + throws EntryInformationException, ReadOnlyException { + try { + final Location new_location = range.createLocation (); + + final QualifierVector qualifiers = new QualifierVector (); + + qualifiers.setQualifier (new Qualifier ("note", "none")); + + final Feature new_feature = + entry.createFeature (key, new_location, qualifiers); + + final CodonUsageAlgorithm codon_usage_algorithm = + base_plot_group.getCodonUsageAlgorithm (); + + if (codon_usage_algorithm != null) { + int score = + (int) (codon_usage_algorithm.getFeatureScore (new_feature) * 50); + + if (score < 0) { + score = 0; + } + + if (score > 100) { + score = 100; + } + + final String score_string = String.valueOf (score); + new_feature.addQualifierValues (new Qualifier ("score", + score_string)); + + final int var_colour = 255 - score * 5 / 2; + final String colour_string = var_colour + " " + var_colour + " 255"; + new_feature.addQualifierValues (new Qualifier ("colour", + colour_string)); + } + + return new_feature; + + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + + /** + * This method will ask the user for a BasePattern (using a TextRequester + * component) then search the sequence for the given pattern and make a new + * feature from each match. The new features will created in an Entry + * called "matches: <pattern>". + **/ + private void makeFeaturesFromPattern () { + final TextRequester text_requester = + new TextRequester ("create features from this pattern:", 18, ""); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + final String pattern_string = event.getRequesterText ().trim (); + + try { + if (pattern_string.length () == 0) { + new MessageDialog (getParentFrame (), "the pattern is too short"); + return; + } + + final BasePattern pattern = new BasePattern (pattern_string); + + makeFeaturesFromPattern (pattern); + } catch (BasePatternFormatException e) { + new MessageDialog (getParentFrame (), + "Illegal base pattern: " + + pattern_string); + } + } + }); + + text_requester.show (); + } + + /** + * Search the sequence for the given pattern and make a new feature from + * each match. The new features will created in an Entry called "matches: + * <pattern>". + **/ + private void makeFeaturesFromPattern (final BasePattern pattern) { + final MarkerRangeVector matches = + pattern.findMatches (entry_group.getBases (), + null, // search from start + entry_group.getSequenceLength ()); + + if (matches.size () == 0) { + new MessageDialog (getParentFrame (), + "no matches found for: " + pattern); + return; + } + + final int TOO_MANY_MATCHES = 100; + + if (matches.size () > TOO_MANY_MATCHES) { + final YesNoDialog dialog = + new YesNoDialog (getParentFrame (), + matches.size () + " matches, continue?"); + + if (dialog.getResult ()) { + // yes - continue + } else { + // no + return; + } + } + + final Entry new_entry = entry_group.createEntry ("matches: " + pattern); + + final Key key = new_entry.getEntryInformation ().getDefaultKey (); + + for (int i = 0 ; i < matches.size () ; ++i) { + try { + final Feature new_feature = + makeFeatureFromMarkerRange (new_entry, matches.elementAt (i), key); + new_feature.setQualifier (new Qualifier ("note", pattern.toString ())); + } catch (EntryInformationException e) { + new MessageDialog (getParentFrame (), "cannot continue: " + + e.getMessage ()); + return; + } catch (ReadOnlyException e) { + new MessageDialog (getParentFrame (), "cannot continue: " + + "the default entry is read only"); + return; + } + } + } + + /** + * Create a misc_feature for each block of ambiguous bases. The new + * features will created in an Entry called "ambiguous bases". + **/ + private void markAmbiguities () { + Entry new_entry = null; + + final Bases bases = entry_group.getBases (); + + for (int i = 1 ; i <= bases.getLength () ; ++i) { + try { + if (! Bases.isLegalBase (bases.getBaseAt (i))) { + final int start_index = i; + + while (i < bases.getLength () && + ! Bases.isLegalBase (bases.getBaseAt (i + 1))) { + ++i; + } + + final int end_index = i; + + if (new_entry == null) { + new_entry = entry_group.createEntry ("ambiguous bases"); + } + + final Range range = new Range (start_index, end_index); + + final String unsure_bases = + bases.getSubSequence (range, Bases.FORWARD); + + final Location location = new Location (range); + + final QualifierVector qualifiers = new QualifierVector (); + + qualifiers.setQualifier (new Qualifier ("note", unsure_bases)); + + final Feature feature = + new_entry.createFeature (new Key ("unsure"), location, qualifiers); + } + } catch (ReadOnlyException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (EntryInformationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + + if (new_entry == null) { + new MessageDialog (getParentFrame (), "No ambiguities found"); + } else { + if (new_entry.getFeatureCount () == 1) { + new MessageDialog (getParentFrame (), "Created one feature"); + + } else { + new MessageDialog (getParentFrame (), "Created " + + new_entry.getFeatureCount () + " features"); + + } + } + } + + /** + * Return the GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource getGotoEventSource () { + return goto_event_source; + } + + /** + * The GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource goto_event_source = null; + + /** + * The EntryGroup object that was passed to the constructor. + **/ + private EntryGroup entry_group; + + private JMenuItem new_feature_item = null; + private JMenuItem new_entry_item = null; + private JMenuItem create_feature_from_range_item = null; + private JMenuItem create_intron_features_item = null; + private JMenuItem create_exon_features_item = null; + private JMenuItem create_gene_features_item = null; + private JMenuItem mark_orfs_with_size_item = null; + private JMenuItem mark_empty_orfs_with_size_item = null; + private JMenuItem mark_orfs_range_item = null; + private JMenuItem mark_pattern_item = null; + private JMenuItem mark_ambiguities_item = null; + + private BasePlotGroup base_plot_group; +} diff --git a/uk/ac/sanger/artemis/components/AlignMatchViewer.java b/uk/ac/sanger/artemis/components/AlignMatchViewer.java new file mode 100644 index 000000000..12297cff6 --- /dev/null +++ b/uk/ac/sanger/artemis/components/AlignMatchViewer.java @@ -0,0 +1,366 @@ +/* AlignMatchViewer.java + * + * created: Tue Feb 13 2001 + * + * This file is part of Artemis + * + * Copyright (C) 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/AlignMatchViewer.java,v 1.1 2004-06-09 09:45:58 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +import java.util.Comparator; +import java.util.Collections; + +/** + * A component for viewing AlignMatchVectors selected in an AlignmentViewer. + * + * @author Kim Rutherford + * @version $Id: AlignMatchViewer.java,v 1.1 2004-06-09 09:45:58 tjc Exp $ + **/ + +public class AlignMatchViewer extends JFrame { + /** + * Create a new AlignMatchViewer which shows the given matches. + * @param alignment_viewer The AlignmentViewer to call alignAt () and + * setSelection () on when the user clicks on a match. + * @param matches The Vector of AlignMatch objects to show. + **/ + public AlignMatchViewer (final AlignmentViewer alignment_viewer, + final AlignMatchVector matches) { + this.alignment_viewer = alignment_viewer; + this.matches = matches; + + final JMenuBar menu_bar = new JMenuBar (); + + final JMenu file_menu = new JMenu ("File"); + + final JMenuItem close = new JMenuItem ("Close"); + close.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + setVisible (false); + AlignMatchViewer.this.dispose (); + } + }); + + file_menu.add (close); + + menu_bar.add (file_menu); + + final JMenu sort_menu = new JMenu ("Sort"); + + sort_menu.add (sort_by_score_menu_item); + + sort_by_score_menu_item.addItemListener (new ItemListener () { + public void itemStateChanged(ItemEvent e) { + if (sort_by_score_menu_item.getState ()) { + sort_by_percent_id_menu_item.setState (false); + sort_by_query_start.setState (false); + sort_by_subject_start.setState (false); + } + + setList (); + } + }); + + sort_menu.add (sort_by_percent_id_menu_item); + + sort_by_percent_id_menu_item.addItemListener (new ItemListener () { + public void itemStateChanged(ItemEvent e) { + if (sort_by_percent_id_menu_item.getState ()) { + sort_by_score_menu_item.setState (false); + sort_by_query_start.setState (false); + sort_by_subject_start.setState (false); + } + + setList (); + } + }); + + sort_menu.add (sort_by_query_start); + + sort_by_query_start.addItemListener (new ItemListener () { + public void itemStateChanged(ItemEvent e) { + if (sort_by_query_start.getState ()) { + sort_by_percent_id_menu_item.setState (false); + sort_by_score_menu_item.setState (false); + sort_by_subject_start.setState (false); + } + + setList (); + } + }); + + sort_menu.add (sort_by_subject_start); + + sort_by_subject_start.addItemListener (new ItemListener () { + public void itemStateChanged(ItemEvent e) { + if (sort_by_subject_start.getState ()) { + sort_by_percent_id_menu_item.setState (false); + sort_by_score_menu_item.setState (false); + sort_by_query_start.setState (false); + } + + setList (); + } + }); + + menu_bar.add (sort_menu); + + setJMenuBar (menu_bar); + + list = new List (); + + list.setBackground (Color.white); + + getContentPane ().add (list, "Center"); + + final JPanel panel = new JPanel (); + + final JButton close_button = new JButton ("Close"); + + panel.add (close_button); + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + AlignMatchViewer.this.dispose (); + } + }); + + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + AlignMatchViewer.this.dispose (); + } + }); + + getContentPane ().add (panel, "South"); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + AlignMatchViewer.this.dispose (); + } + }); + + pack (); + + setList (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + int screen_height = screen.height; + int screen_width = screen.width; + + if (screen_height <= 600) { + setSize (350, screen_height * 9 / 10); + } else { + setSize (350, screen_height - 200); + } + + setLocation (new Point (screen.width - getSize ().width - 5, + (screen.height - getSize ().height) / 2)); + } + + /** + * Sort the matches depending on the setting of sort_by_score_menu_item and + * sort_by_percent_id_menu_item. + **/ + private AlignMatchVector getSortedMatches () { + final AlignMatchVector matches_copy = (AlignMatchVector) matches.clone (); + + final Comparator comparator; + + if (sort_by_score_menu_item.getState ()) { + comparator = + new Comparator () { + public int compare (Object fst, Object snd) { + final int fst_score = ((AlignMatch) fst).getScore (); + final int snd_score = ((AlignMatch) snd).getScore (); + + if (fst_score < snd_score) { + return 1; + } else { + if (fst_score > snd_score) { + return -1; + } else { + return 0; + } + } + } + }; + } else { + if (sort_by_percent_id_menu_item.getState ()) { + comparator = + new Comparator () { + public int compare (Object fst, Object snd) { + final int fst_value = ((AlignMatch) fst).getPercentID (); + final int snd_value = ((AlignMatch) snd).getPercentID (); + + if (fst_value < snd_value) { + return 1; + } else { + if (fst_value > snd_value) { + return -1; + } else { + return 0; + } + } + } + }; + } else { + if (sort_by_query_start.getState ()) { + comparator = + new Comparator () { + public int compare (Object fst, Object snd) { + final int fst_value = + ((AlignMatch) fst).getQuerySequenceStart (); + final int snd_value = + ((AlignMatch) snd).getQuerySequenceStart (); + + if (fst_value > snd_value) { + return 1; + } else { + if (fst_value < snd_value) { + return -1; + } else { + return 0; + } + } + } + }; + } else { + if (sort_by_subject_start.getState ()) { + comparator = + new Comparator () { + public int compare (Object fst, Object snd) { + final int fst_value = + ((AlignMatch) fst).getSubjectSequenceStart (); + final int snd_value = + ((AlignMatch) snd).getSubjectSequenceStart (); + + if (fst_value > snd_value) { + return 1; + } else { + if (fst_value < snd_value) { + return -1; + } else { + return 0; + } + } + } + }; + } else { + return matches; + } + } + } + } + + matches_copy.sort (comparator); + + return matches_copy; + } + + /** + * Clear the List and then fill it with the matches in the order + **/ + private void setList () { + final AlignMatchVector sorted_matches = getSortedMatches (); + + list.setEnabled (false); + list.setVisible (false); + list.removeAll (); + + for (int i = 0 ; i < sorted_matches.size () ; ++i) { + final AlignMatch this_align_match = sorted_matches.elementAt (i); + + String match_string = + this_align_match.getQuerySequenceStart () + ".." + + this_align_match.getQuerySequenceEnd () + " -> " + + this_align_match.getSubjectSequenceStart () + ".." + + this_align_match.getSubjectSequenceEnd () + " " + + (this_align_match.isRevMatch () ? "rev " : "") + + this_align_match.getPercentID () + "% id score " + + this_align_match.getScore (); + + list.add (match_string); + } + + list.addItemListener (new ItemListener () { + public void itemStateChanged (final ItemEvent _) { + final int item_number = list.getSelectedIndex (); + + final AlignMatch selected_match = matches.elementAt (item_number); + + alignment_viewer.setSelection (selected_match); + alignment_viewer.alignAt (selected_match); + } + }); + + list.setEnabled (true); + list.setVisible (true); + } + + /** + * The AlignmentViewer to call alignAt () and setSelection () on when the + * user clicks on a match. + **/ + private AlignmentViewer alignment_viewer = null; + + /** + * The Vector of AlignMatch objects that was passed to the constructor. + **/ + private AlignMatchVector matches = null; + + /** + * The list that contains the matches. + **/ + private List list; + + /** + * If selected the list items will be sorted by score. + **/ + private JCheckBoxMenuItem sort_by_score_menu_item = + new JCheckBoxMenuItem ("Sort by Score"); + + /** + * If selected the list items will be sorted by percent identity. + **/ + private JCheckBoxMenuItem sort_by_percent_id_menu_item = + new JCheckBoxMenuItem ("Sort by Percent Identity"); + + /** + * If selected the list items will be sorted by the start position of the + * query. + **/ + private JCheckBoxMenuItem sort_by_query_start = + new JCheckBoxMenuItem ("Sort by Hit Query Start"); + + /** + * If selected the list items will be sorted by the start position of the + * subject. + **/ + private JCheckBoxMenuItem sort_by_subject_start = + new JCheckBoxMenuItem ("Sort by Hit Subject start"); +} diff --git a/uk/ac/sanger/artemis/components/AlignmentEvent.java b/uk/ac/sanger/artemis/components/AlignmentEvent.java new file mode 100644 index 000000000..0cbf0e2f0 --- /dev/null +++ b/uk/ac/sanger/artemis/components/AlignmentEvent.java @@ -0,0 +1,57 @@ +/* AlignmentEvent.java + * + * created: Mon Sep 10 2001 + * + * This file is part of Artemis + * + * Copyright (C) 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/AlignmentEvent.java,v 1.1 2004-06-09 09:45:59 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +/** + * This event is generated when an AlignmentViewer centres on a particular + * AlignMatch. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: AlignmentEvent.java,v 1.1 2004-06-09 09:45:59 tjc Exp $ + **/ + +public class AlignmentEvent { + /** + * Create a new AlignmentEvent and store the given AlignMatch. + **/ + public AlignmentEvent (AlignMatch match) { + this.match = match; + } + + /** + * Return the AlignMatch that was passed to the constructor. + **/ + public AlignMatch getMatch () { + return match; + } + + /** + * The AlignMatch that was passed to the constructor. + **/ + final private AlignMatch match; +} diff --git a/uk/ac/sanger/artemis/components/AlignmentListener.java b/uk/ac/sanger/artemis/components/AlignmentListener.java new file mode 100644 index 000000000..30618170f --- /dev/null +++ b/uk/ac/sanger/artemis/components/AlignmentListener.java @@ -0,0 +1,43 @@ +/* AlignmentListener.java + * + * created: Mon Sep 10 2001 + * + * This file is part of Artemis + * + * Copyright (C) 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/AlignmentListener.java,v 1.1 2004-06-09 09:46:00 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * This interface is implemented by those objects that wish to know when an + * AlignmentViewer centres on a particular AlignMatch + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: AlignmentListener.java,v 1.1 2004-06-09 09:46:00 tjc Exp $ + **/ + +public interface AlignmentListener { + /** + * Called when an AlignmentViewer centres on a particular AlignMatch. The + * Comparator component implements this so that it can scroll the + * FeatureDisplay components appropriately. + **/ + public void alignMatchChosen (AlignmentEvent e); +} diff --git a/uk/ac/sanger/artemis/components/AlignmentViewer.java b/uk/ac/sanger/artemis/components/AlignmentViewer.java new file mode 100644 index 000000000..b53ca88e1 --- /dev/null +++ b/uk/ac/sanger/artemis/components/AlignmentViewer.java @@ -0,0 +1,1604 @@ +/* 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.1 2004-06-09 09:46:01 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.OutOfRangeException; +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 javax.swing.*; + +/** + * This component shows an alignment of two sequences using the data from a + * ComparisonData object. + * + * @author Kim Rutherford + * @version $Id: AlignmentViewer.java,v 1.1 2004-06-09 09:46:01 tjc Exp $ + **/ + +public class AlignmentViewer extends CanvasPanel + implements SequenceChangeListener { + /** + * 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; + this.all_matches = getComparisonData ().getMatches (); + + 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 (); + + final SelectionChangeListener subject_listener = + new SelectionChangeListener () { + public void selectionChanged (SelectionChangeEvent event) { + final RangeVector ranges = subject_selection.getSelectionRanges (); + selectFromSubjectRanges (ranges); + } + }; + + final SelectionChangeListener query_listener = + new SelectionChangeListener () { + public void selectionChanged (SelectionChangeEvent event) { + final RangeVector ranges = query_selection.getSelectionRanges (); + selectFromQueryRanges (ranges); + } + }; + + makeColours (); + + 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 (); + + getCanvas ().addMouseListener (new MouseAdapter () { + public void mousePressed (final MouseEvent event) { + // on windows we have to check isPopupTrigger in mouseReleased(), + // but do it in mousePressed() on UNIX + if (isMenuTrigger (event)) { + popupMenu (event); + } else { + handleCanvasMousePress (event); + } + } + }); + + getCanvas ().addMouseMotionListener (new MouseMotionAdapter () { + public void mouseDragged (final MouseEvent event) { + if (!modifiersForLockToggle (event)) { + if (!event.isShiftDown ()) { + selected_matches = null; + toggleSelection (event.getPoint ()); + } + repaintCanvas (); + } + } + }); + + scroll_bar = new JScrollBar (Scrollbar.VERTICAL); + scroll_bar.setValues (1, 1, 1, 1000); + scroll_bar.setBlockIncrement (10); + + scroll_bar.addAdjustmentListener (new AdjustmentListener () { + public void adjustmentValueChanged(AdjustmentEvent e) { + repaintCanvas (); + } + }); + + maximum_score = getComparisonData ().getMaximumScore (); + + add (scroll_bar, "East"); + } + + /** + * 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) { + return; + } + + selected_matches = null; + + for (int range_index = 0 ; + range_index < select_ranges.size () ; + ++range_index) { + + final Range select_range = select_ranges.elementAt (range_index); + + for (int match_index = 0 ; + match_index < all_matches.length ; + ++match_index) { + final AlignMatch this_match = all_matches [match_index]; + + if (!isVisible (this_match)) { + // not visible + continue; + } + + final Strand current_subject_forward_strand = + getSubjectForwardStrand (); + + final int subject_length = + current_subject_forward_strand.getSequenceLength (); + + int subject_sequence_start = + getRealSubjectSequenceStart (this_match, + subject_length, + (getOrigSubjectForwardStrand () != + current_subject_forward_strand)); + int subject_sequence_end = + getRealSubjectSequenceEnd (this_match, + subject_length, + (getOrigSubjectForwardStrand () != + current_subject_forward_strand)); + + if (subject_sequence_end < subject_sequence_start) { + final int tmp = subject_sequence_start; + subject_sequence_start = subject_sequence_end; + subject_sequence_end = tmp; + } + + if (select_range.getStart () < subject_sequence_start && + select_range.getEnd () < subject_sequence_start) { + continue; + } + + if (select_range.getStart () > subject_sequence_end && + select_range.getEnd () > subject_sequence_end) { + continue; + } + + if (selected_matches == null) { + selected_matches = new AlignMatchVector (); + } + + if (!selected_matches.contains (this_match)) { + selected_matches.add (this_match); + } + } + } + + selectionChanged (); + } + + /** + * Select those matches that overlap the given range on the query sequence. + **/ + public void selectFromQueryRanges (final RangeVector select_ranges) { + if (disable_selection_from_ranges) { + return; + } + + selected_matches = null; + + for (int range_index = 0 ; + range_index < select_ranges.size () ; + ++range_index) { + + final Range select_range = select_ranges.elementAt (range_index); + + for (int match_index = 0 ; + match_index < all_matches.length ; + ++match_index) { + final AlignMatch this_match = all_matches [match_index]; + + if (!isVisible (this_match)) { + // not visible + continue; + } + + final Strand current_query_forward_strand = getQueryForwardStrand (); + + final int query_length = + current_query_forward_strand.getSequenceLength (); + + 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; + } + + if (select_range.getStart () < query_sequence_start && + select_range.getEnd () < query_sequence_start) { + continue; + } + + if (select_range.getStart () > query_sequence_end && + select_range.getEnd () > query_sequence_end) { + continue; + } + + if (selected_matches == null) { + selected_matches = new AlignMatchVector (); + } + + if (!selected_matches.contains (this_match)) { + selected_matches.add (this_match); + } + } + } + + selectionChanged (); + } + + /** + * Select the given match and move it to the top of the display. + **/ + public void setSelection (final AlignMatch match) { + selected_matches = new AlignMatchVector (); + + selected_matches.add (match); + + selectionChanged (); + } + + /** + * This method tells this AlignmentViewer component where the subject + * sequence is now. + **/ + public void setSubjectSequencePosition (final DisplayAdjustmentEvent event) { + last_subject_event = event; + repaintCanvas (); + } + + /** + * This method tells this AlignmentViewer component where the query + * sequence is now. + **/ + public void setQuerySequencePosition (final DisplayAdjustmentEvent event) { + last_query_event = event; + repaintCanvas (); + } + + /** + * Implementation of the SequenceChangeListener interface. The display is + * redrawn if there is an event. + **/ + public void sequenceChanged (final SequenceChangeEvent event) { + repaintCanvas (); + } + + /** + * Return true if and only if the given MouseEvent (a mouse press) should + * pop up a JPopupMenu. + **/ + private boolean isMenuTrigger (final MouseEvent event) { + if (event.isPopupTrigger () || + (event.getModifiers () & InputEvent.BUTTON3_MASK) != 0) { + return true; + } else { + return false; + } + } + + /** + * Popup a menu. + **/ + private void popupMenu (final MouseEvent event) { + final JPopupMenu popup = new JPopupMenu (); + + final JMenuItem alignmatch_list_item = + new JMenuItem ("View Selected Matches"); + + popup.add (alignmatch_list_item); + + alignmatch_list_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + if (selected_matches == null) { + new MessageFrame ("No matches selected").setVisible (true); + } else { + final AlignMatchVector matches = + (AlignMatchVector) selected_matches.clone (); + + final AlignMatchViewer viewer = + new AlignMatchViewer (AlignmentViewer.this, matches); + + viewer.setVisible (true); + } + } + }); + + final JMenuItem flip_subject_item = + new JMenuItem ("Flip Subject Sequence"); + + popup.add (flip_subject_item); + + flip_subject_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + if (getSubjectDisplay ().isRevCompDisplay ()) { + getSubjectDisplay ().setRevCompDisplay (false); + } else { + getSubjectDisplay ().setRevCompDisplay (true); + } + } + }); + + 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 lock_item = new JMenuItem ("Lock Sequences"); + + popup.add (lock_item); + + lock_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + lockDisplays (); + } + }); + + final JMenuItem unlock_item = new JMenuItem ("Unlock Sequences"); + + popup.add (unlock_item); + + unlock_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + unlockDisplays (); + } + }); + + final JMenuItem cutoffs_item = new JMenuItem ("Set Score Cutoffs ..."); + + popup.add (cutoffs_item); + + cutoffs_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + final ScoreChangeListener minimum_listener = + new ScoreChangeListener () { + public void scoreChanged (final ScoreChangeEvent event) { + minimum_score = event.getValue (); + repaintCanvas (); + } + }; + + final ScoreChangeListener maximum_listener = + new ScoreChangeListener () { + public void scoreChanged (final ScoreChangeEvent event) { + maximum_score = event.getValue (); + repaintCanvas (); + } + }; + + final ScoreChanger score_changer = + 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 _) { + final ScoreChangeListener minimum_listener = + new ScoreChangeListener () { + public void scoreChanged (final ScoreChangeEvent event) { + minimum_percent_id = event.getValue (); + repaintCanvas (); + } + }; + + final ScoreChangeListener maximum_listener = + new ScoreChangeListener () { + public void scoreChanged (final ScoreChangeEvent event) { + maximum_percent_id = event.getValue (); + repaintCanvas (); + } + }; + + final ScoreChanger score_changer = + new ScoreChanger ("Percent Identity Cutoffs", + minimum_listener, maximum_listener, + 0, 100); + + score_changer.setVisible (true); + } + }); + + popup.addSeparator (); + + final JCheckBoxMenuItem offer_to_flip_item = + new JCheckBoxMenuItem ("Offer To RevComp", offer_to_flip_flag); + + offer_to_flip_item.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent e) { + offer_to_flip_flag = !offer_to_flip_flag; + } + }); + + popup.add (offer_to_flip_item); + + final JCheckBoxMenuItem ignore_self_match_item = + new JCheckBoxMenuItem ("Ignore Self Matches"); + + ignore_self_match_item.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent event) { + ignore_self_match_flag = ignore_self_match_item.getState (); + repaintCanvas (); + } + }); + + ignore_self_match_item.setState (ignore_self_match_flag); + + popup.add (ignore_self_match_item); + + getCanvas ().add (popup); + popup.show (getCanvas (), event.getX (), event.getY ()); + } + + + /** + * 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) { + return; + } + + if (event.getClickCount () == 2) { + handleCanvasDoubleClick (event); + } else { + handleCanvasSingleClick (event); + } + + repaintCanvas (); + } + + /** + * Handle a double click on the canvas. + **/ + private void handleCanvasDoubleClick (final MouseEvent event) { + if (selected_matches != null) { + // there should be only one match in the array because + // handleCanvasSingleClick () has been called + alignAt (selected_matches.elementAt (0)); + } + } + + /** + * Send an AlignmentEvent to all the AlignmentListeners. + * @param align_match The AlignMatch that we have just centred on. + **/ + public void alignAt (final AlignMatch align_match) { + 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 (); + } + + for (int i = 0 ; i < targets.size () ; ++i) { + final AlignmentListener listener = + (AlignmentListener) 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 ()) { + selected_matches = null; + } + toggleSelection (event.getPoint ()); + } + } + + /** + * Add or remove the match at the given mouse position to the selection. + **/ + private void toggleSelection (final Point point) { + final AlignMatch clicked_align_match = + getAlignMatchFromPosition (point); + + if (clicked_align_match != null) { + if (selected_matches == null) { + selected_matches = new AlignMatchVector (); + selected_matches.add (clicked_align_match); + } else { + if (selected_matches.contains (clicked_align_match)) { + selected_matches.remove (clicked_align_match); + if (selected_matches.size () == 0) { + selected_matches = null; + } + } else { + selected_matches.add (clicked_align_match); + } + } + + } + + selectionChanged (); + } + + /** + * 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. + **/ + private AlignMatch getAlignMatchFromPosition (final Point click_point) { + final int canvas_height = getCanvas ().getSize ().height; + final int canvas_width = getCanvas ().getSize ().width; + + for (int i = all_matches.length - 1 ; i >= 0 ; --i) { + final AlignMatch this_match = all_matches [i]; + + final int [] match_x_positions = + getMatchCoords (canvas_width, this_match); + + if (match_x_positions == null) { + continue; + } + + if (!isVisible (this_match)) { + continue; + } + + final int subject_start_x = match_x_positions[0]; + 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) { + return this_match; + } + } + + return null; + } + + /** + * 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 + * repaintCanvas (). + **/ + private void selectionChanged () { + for (int i = 0 ; i < selection_change_listeners.size () ; ++i) { + final AlignMatchVector matches = + (AlignMatchVector) selected_matches.clone (); + + final AlignmentSelectionChangeEvent ev = + new AlignmentSelectionChangeEvent (this, matches); + + final AlignmentSelectionChangeListener listener = + (AlignmentSelectionChangeListener) selection_change_listeners.elementAt (i); + + listener.alignmentSelectionChanged (ev); + } + + if (selected_matches != null && selected_matches.size () > 0) { + // a count of the number of selected matches seen so far + int seen_and_selected_count = 0; + + for (int i = 0 ; i < all_matches.length ; ++i) { + final AlignMatch this_match = all_matches[i]; + + if (selected_matches.contains (this_match)) { + ++seen_and_selected_count; + } else { + if (seen_and_selected_count > 0) { + // move the matches down to fill the gap + all_matches[i-seen_and_selected_count] = all_matches[i]; + } + } + } + + // put the selected_matches at the end of all_matches + for (int i = 0 ; i < selected_matches.size () ; ++i) { + all_matches[all_matches.length - selected_matches.size () + i] = + selected_matches.elementAt (i); + } + } + + repaintCanvas (); + } + + /** + * Add the AlignmentSelectionChangeListener to the list of objects + * listening for AlignmentSelectionChangeEvents. + **/ + public void + addAlignmentSelectionChangeListener (final AlignmentSelectionChangeListener l) { + selection_change_listeners.addElement (l); + } + + /** + * Remove the AlignmentSelectionChangeListener from the list of objects + * listening for AlignmentSelectionChangeEvents. + **/ + public void + removeAlignmentSelectionChangeListener (final AlignmentSelectionChangeListener l) { + selection_change_listeners.removeElement (l); + } + + /** + * Adds the specified AlignmentEvent listener to receive events from this + * object. + * @param l the listener. + **/ + public void addAlignmentListener (AlignmentListener l) { + alignment_event_listeners.addElement (l); + } + + /** + * Removes the specified AlignmentEvent listener so that it no longer + * receives events from this object. + * @param l the listener. + **/ + public void removeAlignmentListener (AlignmentListener l) { + alignment_event_listeners.removeElement (l); + } + + /** + * Returns true if and only if we should offer to flip the query + * sequence when the user double clicks on a flipped match. + **/ + public boolean offerToFlip () { + return offer_to_flip_flag; + } + + /** + * The main paint function for the canvas. An off screen image used for + * double buffering when drawing the canvas. + * @param g The Graphics object of the canvas. + **/ + protected void paintCanvas (final Graphics g) { + fillBackground (g); + + if (last_subject_event != null && last_query_event != null) { + drawAlignments (g); + drawLabels (g); + } + } + + /** + * Draw the background colour of the frames. + **/ + private void fillBackground (final Graphics g) { + g.setColor (Color.white); + + g.fillRect (0, 0, getCanvas ().getSize ().width, + getCanvas ().getSize ().height); + } + + /** + * Draw the labels into the given Graphics object. There is a label at the + * top left showing info about the current AlignMatch object, + * + * XXX + * a label at + * the bottom left showing whether or not the query sequence is reverse + * complemented + * XXX + * + * and a label beside the Scrollbar showing the current score + * cutoff. + **/ + private void drawLabels (final Graphics g) { + final FontMetrics fm = g.getFontMetrics (); + final int canvas_width = getCanvas ().getSize ().width; + final int canvas_height = getCanvas ().getSize ().height; + + final String cutoff_label = + Integer.toString (scroll_bar.getValue ()); + + final int cutoff_label_width = fm.stringWidth (cutoff_label); + + int cutoff_label_position = + (int)((scroll_bar.getValue () - + scroll_bar.getMinimum ()) / (1.0 * + (scroll_bar.getMaximum () - + scroll_bar.getMinimum ())) * canvas_height); + + if (cutoff_label_position < getFontAscent ()) { + cutoff_label_position = getFontAscent (); + } + + + final int [] cutoff_x_points = { + canvas_width - cutoff_label_width, + canvas_width - 2, + canvas_width - 2, + canvas_width - cutoff_label_width, + }; + + final int [] cutoff_y_points = { + cutoff_label_position + 1, + cutoff_label_position + 1, + cutoff_label_position - getFontAscent (), + cutoff_label_position - getFontAscent (), + }; + + g.setColor (Color.white); + g.fillPolygon (cutoff_x_points, cutoff_y_points, 4); + + g.setColor (Color.black); + + g.drawString (cutoff_label, canvas_width - cutoff_label_width, + cutoff_label_position); + + final int font_height = getFontAscent () + getFontDescent (); + + if (selected_matches != null) { + final String match_string_1; + + if (selected_matches.size () > 1) { + match_string_1 = selected_matches.size () + " matches selected"; + } else { + final AlignMatch selected_align_match = selected_matches.elementAt (0); + + match_string_1 = + selected_align_match.getQuerySequenceStart () + ".." + + selected_align_match.getQuerySequenceEnd () + " -> " + + selected_align_match.getSubjectSequenceStart () + ".." + + selected_align_match.getSubjectSequenceEnd (); + } + + final int match_string_1_width = fm.stringWidth (match_string_1); + + final int [] match_1_x_points = { + 0, 0, match_string_1_width, match_string_1_width + }; + + final int [] match_1_y_points = { + 0, font_height, font_height, 0 + }; + + g.setColor (Color.white); + g.fillPolygon (match_1_x_points, match_1_y_points, 4); + + g.setColor (Color.black); + g.drawString (match_string_1, 0, getFontAscent ()); + + if (selected_matches.size () == 1) { + final AlignMatch selected_align_match = selected_matches.elementAt (0); + + final String match_string_2 = "score: " + + selected_align_match.getScore () + " percent id: " + + selected_align_match.getPercentID () + "%"; + + final int match_string_2_width = fm.stringWidth (match_string_2); + + final int [] match_2_x_points = { + 0, 0, match_string_2_width, match_string_2_width + }; + + final int [] match_2_y_points = { + font_height, font_height * 2, font_height * 2, font_height + }; + + g.setColor (Color.white); + g.fillPolygon (match_2_x_points, match_2_y_points, 4); + + g.setColor (Color.black); + g.drawString (match_string_2, 0, getFontAscent () + font_height); + } + } + + final StringVector status_strings = new StringVector (); + + if (displaysAreLocked ()) { + status_strings.add ("LOCKED"); + } + + if (getSubjectDisplay ().isRevCompDisplay ()) { + status_strings.add ("Subject: Flipped"); + } + + if (getQueryDisplay ().isRevCompDisplay ()) { + status_strings.add ("Query: Flipped"); + } + + if (getOrigSubjectForwardStrand () != getSubjectForwardStrand ()) { + status_strings.add ("Subject: Reverse Complemented"); + } + + if (getOrigQueryForwardStrand () != getQueryForwardStrand ()) { + status_strings.add ("Query: Reverse Complemented"); + } + + g.setColor (Color.white); + + for (int i = 0 ; i < status_strings.size () ; ++i) { + final String status_string = status_strings.elementAt (i); + + final int status_string_width = fm.stringWidth (status_string); + + final int [] x_points = { + 0, 0, status_string_width, status_string_width + }; + + final int string_offset = font_height * (status_strings.size () - i - 1); + + final int [] y_points = { + canvas_height - string_offset, + canvas_height - font_height - string_offset, + canvas_height - font_height - string_offset, + canvas_height - string_offset + }; + + g.fillPolygon (x_points, y_points, 4); + } + + g.setColor (Color.black); + + for (int i = 0 ; i < status_strings.size () ; ++i) { + final String status_string = status_strings.elementAt (i); + + final int string_offset = font_height * (status_strings.size () - i - 1); + + g.drawString (status_string, 0, + canvas_height - string_offset - getFontDescent ()); + } + } + + /** + * Draw the alignments into the given Graphics object. + **/ + private void drawAlignments (final Graphics g) { + final int canvas_height = getCanvas ().getSize ().height; + final int canvas_width = getCanvas ().getSize ().width; + + for (int i = 0 ; i < all_matches.length ; ++i) { + final AlignMatch this_match = all_matches [i]; + + final int [] match_x_positions = + getMatchCoords (canvas_width, this_match); + + if (match_x_positions == null) { + continue; + } + + if (!isVisible (this_match)) { + // not visible + continue; + } + + final int subject_start_x = match_x_positions[0]; + 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]; + + final int [] x_coords = new int [4]; + final int [] y_coords = new int [4]; + + x_coords[0] = subject_start_x; + y_coords[0] = 0; + x_coords[1] = query_start_x; + y_coords[1] = canvas_height; + x_coords[2] = query_end_x; + y_coords[2] = canvas_height; + x_coords[3] = subject_end_x; + y_coords[3] = 0; + + final boolean highlight_this_match; + + if (selected_matches != null && + selected_matches.contains (this_match)) { + highlight_this_match = true; + } else { + highlight_this_match = false; + } + + final int OFFSCREEN = 3000; + + final int percent_id = this_match.getPercentID (); + + if (highlight_this_match) { + g.setColor (Color.yellow); + } else { + if (percent_id == -1) { + if (this_match.isRevMatch ()) { + g.setColor (Color.blue); + } else { + g.setColor (Color.red); + } + } else { + int colour_index = red_percent_id_colours.length - 1; + + if (maximum_percent_id > minimum_percent_id) { + colour_index = + (int)(red_percent_id_colours.length * 0.999 * + (percent_id - minimum_percent_id) / + (maximum_percent_id - minimum_percent_id)); + } + + if (this_match.isRevMatch ()) { + g.setColor (blue_percent_id_colours[colour_index]); + } else { + g.setColor (red_percent_id_colours[colour_index]); + } + } + } + + g.fillPolygon (x_coords, y_coords, x_coords.length); + + if (subject_end_x - subject_start_x < 5 && + subject_end_x - subject_start_x > -5 || + subject_start_x < -OFFSCREEN || + subject_end_x > OFFSCREEN || + query_start_x < -OFFSCREEN || + query_end_x > OFFSCREEN) { + + // match is (probably) narrow so draw the border to the same colour as + // the polygon + } else { + + // draw a black outline the make the match stand out + g.setColor (Color.black); + } + + g.drawLine (subject_start_x, 0, query_start_x, canvas_height); + g.drawLine (subject_end_x, 0, query_end_x, canvas_height); + } + } + + /** + * Return true if and only if the given match is currently visible. + **/ + private boolean isVisible (final AlignMatch match) { + if (ignore_self_match_flag && match.isSelfMatch ()) { + return false; + } + + final int score = match.getScore (); + final int percent_id = match.getPercentID (); + + if (score > -1) { + if (score < minimum_score || score > maximum_score) { + return false; + } + } + + if (percent_id > -1) { + if (percent_id < minimum_percent_id || percent_id > maximum_percent_id) { + return false; + } + } + + final int match_length = + Math.abs (match.getSubjectSequenceStart () - + match.getSubjectSequenceEnd ()); + + if (match_length < scroll_bar.getValue ()) { + return false; + } + + return true; + } + + /** + * Return the start position in the subject sequence of the given + * AlignMatch, taking into account the current orientation of the + * sequences. If the reverse_position argument is true reverse the + * complemented match coordinates will be returned. + **/ + static int getRealSubjectSequenceStart (final AlignMatch match, + final int sequence_length, + final boolean reverse_position) { + if (reverse_position) { + return sequence_length - match.getSubjectSequenceStart () + 1; + } else { + return match.getSubjectSequenceStart (); + } + } + + /** + * Return the end position in the subject sequence of the given AlignMatch, + * taking into account the current orientation of the sequences. If the + * reverse_position argument is true reverse the complemented match + * coordinates will be returned. + **/ + static int getRealSubjectSequenceEnd (final AlignMatch match, + final int sequence_length, + final boolean reverse_position) { + if (reverse_position) { + return sequence_length - match.getSubjectSequenceEnd () + 1; + } else { + return match.getSubjectSequenceEnd (); + } + } + + /** + * Return the start position in the query sequence of the given AlignMatch, + * taking into account the current orientation of the sequences. If the + * reverse_position argument is true reverse the complemented match + * coordinates will be returned. + **/ + static int getRealQuerySequenceStart (final AlignMatch match, + final int sequence_length, + final boolean reverse_position) { + if (reverse_position) { + return sequence_length - match.getQuerySequenceStart () + 1; + } else { + return match.getQuerySequenceStart (); + } + } + + /** + * Return the end position in the query sequence of the given AlignMatch, + * taking into account the current orientation of the sequences. If the + * reverse_position argument is true reverse the complemented match + * coordinates will be returned. + **/ + static int getRealQuerySequenceEnd (final AlignMatch match, + final int sequence_length, + final boolean reverse_position) { + if (reverse_position) { + return sequence_length - match.getQuerySequenceEnd () + 1; + } else { + return match.getQuerySequenceEnd (); + } + } + + /** + * Return the screen x positions of the corners of the given match. The + * order is Top Left, Top Right, Bottom Left, Bottom Right, unless the + * match is an inversion, in which case it will be TL,TR,BR,BL. Returns + * null if and only if the match is not currently visible. + **/ + private int [] getMatchCoords (final int canvas_width, + final AlignMatch this_match) { + final int subject_length = getSubjectForwardStrand ().getSequenceLength (); + final int query_length = getQueryForwardStrand ().getSequenceLength (); + + int subject_sequence_start = + getRealSubjectSequenceStart (this_match, + subject_length, + subjectIsRevComp ()); + int subject_sequence_end = + getRealSubjectSequenceEnd (this_match, + subject_length, + subjectIsRevComp ()); + int query_sequence_start = + getRealQuerySequenceStart (this_match, + query_length, + queryIsRevComp ()); + int query_sequence_end = + getRealQuerySequenceEnd (this_match, + query_length, + queryIsRevComp ()); + + // add one base because we want to draw to the end of the base + if (subjectIsRevComp ()) { + subject_sequence_start += 1; + } else { + subject_sequence_end += 1; + } + + if (this_match.isRevMatch () && !queryIsRevComp () || + !this_match.isRevMatch () && queryIsRevComp ()) { + query_sequence_start += 1; + } else { + query_sequence_end += 1; + } + + final int subject_start_x = + getScreenPosition (canvas_width, last_subject_event, + subject_sequence_start); + final int subject_end_x = + getScreenPosition (canvas_width, last_subject_event, + subject_sequence_end); + + final int query_start_x = + getScreenPosition (canvas_width, last_query_event, + query_sequence_start); + final int query_end_x = + getScreenPosition (canvas_width, last_query_event, + query_sequence_end); + + boolean subject_off_left = false; + boolean subject_off_right = false; + boolean query_off_left = false; + boolean query_off_right = false; + + if (subject_start_x < 0 && subject_end_x < 0) { + subject_off_left = true; + } + + if (subject_start_x >= canvas_width && subject_end_x >= canvas_width) { + subject_off_right = true; + } + + if (query_start_x < 0 && query_end_x < 0) { + query_off_left = true; + } + + if (query_start_x >= canvas_width && query_end_x >= canvas_width) { + query_off_right = true; + } + + if ((subject_off_left ? 1 : 0) + + (query_off_left ? 1 : 0) + + (subject_off_right ? 1 : 0) + + (query_off_right ? 1 : 0) == 2) { + return null; + } else { + final int [] return_values = new int [4]; + + return_values[0] = subject_start_x; + return_values[1] = subject_end_x; + return_values[2] = query_start_x; + return_values[3] = query_end_x; + + return return_values; + } + } + + /** + * Return the current forward Strand of the subject EntryGroup. + **/ + private Strand getSubjectForwardStrand () { + return getSubjectEntryGroup ().getBases ().getForwardStrand (); + } + + /** + * Return the current forward Strand of the query EntryGroup. + **/ + private Strand getQueryForwardStrand () { + return getQueryEntryGroup ().getBases ().getForwardStrand (); + } + + /** + * Return the current reverse Strand of the subject EntryGroup. + **/ + private Strand getSubjectReverseStrand () { + return getSubjectEntryGroup ().getBases ().getReverseStrand (); + } + + /** + * Return the current reverse Strand of the query EntryGroup. + **/ + private Strand getQueryReverseStrand () { + return getQueryEntryGroup ().getBases ().getReverseStrand (); + } + + /** + * Return the subject EntryGroup that was passed to the constructor. + **/ + private EntryGroup getSubjectEntryGroup () { + return subject_entry_group; + } + + /** + * Return the subject EntryGroup that was passed to the constructor. + **/ + private EntryGroup getQueryEntryGroup () { + return query_entry_group; + } + + /** + * Return the reference of the subject FeatureDisplay. + **/ + public FeatureDisplay getSubjectDisplay () { + return subject_feature_display; + } + + /** + * Return the reference of the query FeatureDisplay. + **/ + public FeatureDisplay getQueryDisplay () { + return query_feature_display; + } + + /** + * Returns true if and only if the subject sequence has been flipped since + * this object was created. + **/ + boolean subjectIsRevComp () { + final Strand current_subject_forward_strand = getSubjectForwardStrand (); + + if (getOrigSubjectForwardStrand () == current_subject_forward_strand ^ + getSubjectDisplay ().isRevCompDisplay ()) { + return false; + } else { + return true; + } + } + + /** + * Returns true if and only if the query sequence has been flipped since + * this object was created. + **/ + boolean queryIsRevComp () { + final Strand current_query_forward_strand = getQueryForwardStrand (); + + if (getOrigQueryForwardStrand () == current_query_forward_strand ^ + getQueryDisplay ().isRevCompDisplay ()) { + return false; + } else { + return true; + } + } + + /** + * Return the forward Strand of the subject EntryGroup from when the + * Comparator was created. + **/ + public Strand getOrigSubjectForwardStrand () { + return orig_subject_forward_strand; + } + + /** + * Return the forward Strand of the query EntryGroup from when the + * Comparator was created. + **/ + public Strand getOrigQueryForwardStrand () { + return orig_query_forward_strand; + } + + /** + * Return the reverse Strand of the subject EntryGroup from when the + * Comparator was created. + **/ + public Strand getOrigSubjectReverseStrand () { + return orig_subject_reverse_strand; + } + + /** + * Return the reverse Strand of the query EntryGroup from when the + * Comparator was created. + **/ + public Strand getOrigQueryReverseStrand () { + return orig_query_reverse_strand; + } + + /** + * Arrange for the two FeatureDisplay objects to scroll in parallel. + **/ + public void lockDisplays () { + displays_are_locked = true; + repaintCanvas (); + } + + /** + * Arrange for the two FeatureDisplay objects to scroll independently. + **/ + public void unlockDisplays () { + displays_are_locked = false; + repaintCanvas (); + } + + /** + * Return true if and only if the displays are currently locked. + **/ + public boolean displaysAreLocked () { + return displays_are_locked; + } + + /** + * Toggle whether the two displays are locked. + **/ + public void toggleDisplayLock () { + displays_are_locked = !displays_are_locked; + repaintCanvas (); + } + + /** + * Calling this method 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. + **/ + public void disableSelection () { + disable_selection_from_ranges = true; + } + + /** + * Enable selectFromQueryRange() and selectFromSubjectRange(). + **/ + public void enableSelection () { + disable_selection_from_ranges = false; + } + + /** + * Convert a base position into a screen x coordinate. + **/ + private int getScreenPosition (final int canvas_width, + final DisplayAdjustmentEvent event, + final int base_position) { + // this is the base that is at the left of the screen + final int screen_start_base = event.getStart (); + + final double base_pos_float = + event.getBaseWidth () * (base_position - screen_start_base); + + if (base_pos_float > 30000) { + return 30000; + } else { + if (base_pos_float < -30000) { + return -30000; + } else { + return (int) base_pos_float; + } + } + } + + /** + * Return an array of colours that will be used for colouring the matches + * (depending on score). + **/ + private void makeColours () { + red_percent_id_colours = new Color [NUMBER_OF_SHADES]; + blue_percent_id_colours = new Color [NUMBER_OF_SHADES]; + + for (int i = 0 ; i < blue_percent_id_colours.length ; ++i) { + final int shade_value = 255 - (int) (256 * 1.0 * i / NUMBER_OF_SHADES); + red_percent_id_colours[i] = new Color (255, shade_value, shade_value); + blue_percent_id_colours[i] = new Color (shade_value, shade_value, 255); + } + } + + /** + * Return the ComparisonData object that was passed to the constructor. + **/ + private ComparisonData getComparisonData () { + return comparison_data; + } + + /** + * The 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; + + /** + * The FeatureDisplay that is above this component. (From the constructor). + **/ + private FeatureDisplay subject_feature_display; + + /** + * The 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; + + /** + * The selected matches. null means no matches are selected. + **/ + private AlignMatchVector selected_matches = null; + + /** + * The objects that are listening for AlignmentSelectionChangeEvents. + **/ + private java.util.Vector selection_change_listeners = + new java.util.Vector (); + + /** + * The number of shades of red and blue to use for percentage ID colouring. + **/ + private static int NUMBER_OF_SHADES = 13; + + /** + * The Reds used to display the percent identity of matches. + **/ + private Color [] red_percent_id_colours; + + /** + * The Blues used to display the percent identity of matches. + **/ + private Color [] blue_percent_id_colours; + + /** + * The 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; + + /** + * Matches with percent identity values below this number will not be shown. + **/ + private int minimum_percent_id = 0; + + /** + * Matches with percent identity values above this number will not be shown. + **/ + 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; + + /** + * A 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; +} diff --git a/uk/ac/sanger/artemis/components/ArtemisCheckboxMenuItem.java b/uk/ac/sanger/artemis/components/ArtemisCheckboxMenuItem.java new file mode 100644 index 000000000..fd3bf5357 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ArtemisCheckboxMenuItem.java @@ -0,0 +1,127 @@ +/* CheckboxMenuItem.java + * + * created: Mon Aug 19 2002 + * + * This file is part of Artemis + * + * Copyright (C) 2001,2002 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/ArtemisCheckboxMenuItem.java,v 1.1 2004-06-09 09:46:02 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.awt.*; +import java.awt.event.*; + +import java.util.Vector; + +/** + * This component is a replacement for the CheckboxMenuItem component. It is + * needed on GNU/Linux with versions 1.2, 1.3 and 1.4 or the VM. See this + * bug for more: + * http://developer.java.sun.com/developer/bugParade/bugs/4533641.html + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ArtemisCheckboxMenuItem.java,v 1.1 2004-06-09 09:46:02 tjc Exp $ + **/ + +public class ArtemisCheckboxMenuItem + extends MenuItem + implements ActionListener, ItemSelectable { + /** + * Make a new ArtemisCheckboxMenuItem with the given label. + **/ + public ArtemisCheckboxMenuItem (final String label) { + super (label); + + this.orig_label = label; + + addActionListener (this); + } + + /** + * Implementation of ActionListener. + **/ + public void actionPerformed (ActionEvent _) { + final ItemEvent e; + + if (getState ()) { + setState (false); + e = new ItemEvent (this, ItemEvent.ITEM_STATE_CHANGED, + this, ItemEvent.DESELECTED); + } else { + setState (true); + e = new ItemEvent (this, ItemEvent.ITEM_STATE_CHANGED, + this, ItemEvent.SELECTED); + } + + for (int i = 0 ; i < listeners.size () ; ++i) { + ((ItemListener) listeners.elementAt (i)).itemStateChanged (e) ; + } + } + + /** + * Returns null (implementation of ItemSelectable). + **/ + public Object[] getSelectedObjects() { + return null; + } + + /** + * Sets this check box menu item to the specifed state. The boolean value + * true indicates "on" while false indicates "off." + * @param state true if the check box menu item is on, otherwise false + **/ + public void setState (final boolean state) { + this.state = state; + if (state) { + setLabel ("Disable " + orig_label + " (current on)"); + } else { + setLabel ("Enable " + orig_label + " (current off)"); + } + } + + /** + * Determines whether the state of this check box menu item is "on" or + * "off." + * @return the state of this check box menu item, where true indicates "on" + * and false indicates "off" + **/ + public boolean getState () { + return state; + } + + /** + * Add the given listener. + **/ + public void addItemListener (final ItemListener listener) { + listeners.addElement (listener); + } + + /** + * remove the given listener. + **/ + public void removeItemListener (final ItemListener listener) { + listeners.removeElement (listener); + } + + private String orig_label; + private boolean state = false; + + private Vector listeners = new Vector (); +} diff --git a/uk/ac/sanger/artemis/components/ArtemisMain.java b/uk/ac/sanger/artemis/components/ArtemisMain.java new file mode 100644 index 000000000..c51696133 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ArtemisMain.java @@ -0,0 +1,634 @@ +/* ArtemisMain.java + * + * created: Wed Feb 23 2000 + * + * This file is part of Artemis + * + * Copyright(C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or(at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ArtemisMain.java,v 1.1 2004-06-09 09:46:03 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.NoSequenceException; +import uk.ac.sanger.artemis.sequence.Bases; +import uk.ac.sanger.artemis.util.Document; +import uk.ac.sanger.artemis.util.DocumentFactory; +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.util.InputStreamProgressListener; +import uk.ac.sanger.artemis.io.EntryInformation; + +import org.biojava.bio.seq.io.SequenceFormat; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.io.*; + +/** + * The main window for the Artemis sequence editor. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ArtemisMain.java,v 1.1 2004-06-09 09:46:03 tjc Exp $ + **/ + +public class ArtemisMain extends Splash +{ + /** Version String use for banner messages and title bars. */ + public static final String version = "Swing 1"; + + /** A vector containing all EntryEdit object we have created. */ + private EntryEditVector entry_edit_objects = new EntryEditVector(); + + protected static FileManager filemanager = null; + /** + * The constructor creates all the components for the main Artemis + * window and sets up all the menu callbacks. + **/ + public ArtemisMain() + { + super("Artemis", "Artemis", version); + + ActionListener menu_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + if(filemanager == null) + filemanager = new FileManager(ArtemisMain.this); + else + filemanager.setVisible(true); + } + }; + makeMenuItem(file_menu, "Open File Manager ...", menu_listener); + + final EntrySourceVector entry_sources = getEntrySources(this); + + for(int source_index=0; source_index<entry_sources.size(); + ++source_index) + { + final EntrySource this_entry_source = + entry_sources.elementAt(source_index); + + String entry_source_name = this_entry_source.getSourceName(); + String menu_name = null; + + if(entry_source_name.equals("Filesystem")) + menu_name = "Open ..."; + else + menu_name = "Open from " + entry_source_name + " ..."; + + menu_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + getEntryEditFromEntrySource(this_entry_source); + } + }; + makeMenuItem(file_menu, menu_name, menu_listener); + } + + menu_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + exit(); + } + }; + makeMenuItem(file_menu, "Quit", menu_listener); + +// getCanvas().addMouseListener(new MouseAdapter() { +// /** +// * Listen for mouse press events so that we can do popup menus and +// * selection. +// **/ +// public void mousePressed(MouseEvent event) { +// handleCanvasMousePress(event); +// } +// }); + +// java.util.Properties props = System.getProperties(); +// java.util.Enumeration en = props.propertyNames(); +// while(en.hasMoreElements()) +// { +// String prop = (String)en.nextElement(); +// System.out.println(prop+":: "+props.getProperty(prop)); +// } + + } + + +// XXX add pasteClipboard() one day + +// /** +// * Handle a mouse press event on the drawing canvas - select on click, +// * select and broadcast it on double click. +// **/ +// private void handleCanvasMousePress(MouseEvent event) { +// if(event.getID() != MouseEvent.MOUSE_PRESSED) { +// return; +// } + +// if((event.getModifiers() & InputEvent.BUTTON2_MASK) != 0) { +// pasteClipboard(); +// } +// } + + /** + * Read the entries named in args and in the diana.ini file. + **/ + public void readArgsAndOptions(final String [] args, + ProgressThread progress_thread) + { + if(args.length == 0) + { + // open the entries given in the options file(diana.ini) + readDefaultEntries(); + return; + } + + if(args[0].equals("-biojava")) + { + handleBioJava(args); + return; + } + + final EntryInformation artemis_entry_information = + Options.getArtemisEntryInformation(); + + EntryEdit last_entry_edit = null; + boolean seen_plus = false; + + if(progress_thread != null) + progress_thread.start(); + + for(int i = 0 ; i<args.length ; ++i) + { + String new_entry_name = args[i]; + + if(new_entry_name.length() == 0) + continue; + + if(new_entry_name.equals("+")) + { + seen_plus = true; + continue; + } + + + if(new_entry_name.startsWith("+") && last_entry_edit != null || + seen_plus) + { + // new feature file + + final Document entry_document; + + if(seen_plus) + entry_document = DocumentFactory.makeDocument(new_entry_name); + else + entry_document = + DocumentFactory.makeDocument(new_entry_name.substring(1)); + + final InputStreamProgressListener progress_listener = + getInputStreamProgressListener(); + + entry_document.addInputStreamProgressListener(progress_listener); + + final uk.ac.sanger.artemis.io.Entry new_embl_entry = + EntryFileDialog.getEntryFromFile(this, entry_document, + artemis_entry_information, + false); + + if(new_embl_entry == null) // the read failed + break; + + try + { + final Entry new_entry = + new Entry(last_entry_edit.getEntryGroup().getBases(), + new_embl_entry); + + last_entry_edit.getEntryGroup().add(new_entry); + } + catch(OutOfRangeException e) + { + new MessageDialog(this, "read failed: one of the features in " + + new_entry_name + " has an out of range " + + "location: " + e.getMessage()); + } + } + else + { + // new sequence file + + if(last_entry_edit != null) + { + last_entry_edit.setVisible(true); + last_entry_edit = null; + } + + final Document entry_document = + DocumentFactory.makeDocument(new_entry_name); + + entry_document.addInputStreamProgressListener(getInputStreamProgressListener()); + + final uk.ac.sanger.artemis.io.Entry new_embl_entry = + EntryFileDialog.getEntryFromFile(this, entry_document, + artemis_entry_information, + false); + + if(new_embl_entry == null) // the read failed + break; + + try + { + final Entry entry = new Entry(new_embl_entry); + last_entry_edit = makeEntryEdit(entry); + addEntryEdit(last_entry_edit); + getStatusLabel().setText(""); + } + catch(OutOfRangeException e) + { + new MessageDialog(this, "read failed: one of the features in " + + new_entry_name + " has an out of range " + + "location: " + e.getMessage()); + break; + } + catch(NoSequenceException e) + { + new MessageDialog(this, "read failed: " + + new_entry_name + " contains no sequence"); + break; + } + } + } + + for(int entry_index=0; entry_index<entry_edit_objects.size(); + ++entry_index) + entry_edit_objects.elementAt(entry_index).show(); + } + + /** + * Handle the -biojava option + **/ + private void handleBioJava(final String [] args) + { + if(args.length == 3) + { + final String class_name = args[1]; + final String location = args[2]; + + final Document location_document = + DocumentFactory.makeDocument(location); + + try + { + final Object biojava_object = + Class.forName(class_name).newInstance(); + + final EntryInformation entry_information = + Options.getArtemisEntryInformation(); + + final uk.ac.sanger.artemis.io.BioJavaEntry emblEntry; + + if(biojava_object instanceof SequenceFormat) + { + final SequenceFormat sequence_format = + (SequenceFormat) biojava_object; + + emblEntry = + new uk.ac.sanger.artemis.io.BioJavaEntry(entry_information, + location_document, + sequence_format); + + final Entry new_entry = new Entry(emblEntry); + + final EntryEdit new_entry_edit = makeEntryEdit(new_entry); + + new_entry_edit.setVisible(true); + } + else + new MessageDialog(this, "not a SequenceFormat: " + class_name); + } + catch(IllegalAccessException e) + { + new MessageDialog(this, "cannot create class: " + class_name + + " - IllegalAccessException"); + } + catch(ClassNotFoundException e) + { + new MessageDialog(this, "cannot find class: " + class_name); + } + catch(ClassCastException e) + { + new MessageDialog(this, class_name + " is not a sub-class of " + + "SequenceFormat"); + } + catch(IOException e) + { + new MessageDialog(this, "I/O error while reading from " + + location + ": " + e.getMessage()); + } + catch(NoSequenceException e) + { + new MessageDialog(this, location + " contained no sequence"); + } + catch(InstantiationException e) + { + new MessageDialog(this, "cannot instantiate " + class_name); + } + catch(OutOfRangeException e) + { + new MessageDialog(this, "read failed: one of the features in " + + location + + " has an out of range location: " + + e.getMessage()); + } + } + else + new MessageDialog(this, "the -biojava option needs two arguments"); + } + + /** + * Read the entries given in the uk.ac.sanger.artemis.ini file. + **/ + private void readDefaultEntries() + { + final EntryInformation artemis_entry_information = + Options.getArtemisEntryInformation(); + + final String default_sequence_file_name = + Options.getOptions().getDefaultSequenceFileName(); + + final String default_feature_file_name = + Options.getOptions().getDefaultFeatureFileName(); + + if(default_sequence_file_name != null) + { + final String default_sequence_file_name_embl = + default_sequence_file_name + "_embl"; + + uk.ac.sanger.artemis.io.Entry new_embl_entry = null; + + // try opening the default sequence file with "_embl" added to the name + // if that fails try the plain sequence file name + + final Document entry_document = + DocumentFactory.makeDocument(default_sequence_file_name_embl); + + final InputStreamProgressListener progress_listener = + getInputStreamProgressListener(); + + entry_document.addInputStreamProgressListener(progress_listener); + + if(entry_document.readable()) + { + new_embl_entry = + EntryFileDialog.getEntryFromFile(this, + entry_document, + artemis_entry_information, + false); + } + + if(new_embl_entry == null || new_embl_entry.getSequence() == null || + new_embl_entry.getSequence().length() == 0) + { + final File entry_file = new File(default_sequence_file_name); + + if(entry_file.exists()) + { + new_embl_entry = + EntryFileDialog.getEntryFromFile(this, + entry_document, + artemis_entry_information, + false); + } + else + { + // read failed + System.err.println("file does not exist: " + + default_sequence_file_name + + "(given in options files)"); + return; + } + } + + if(new_embl_entry == null || new_embl_entry.getSequence() == null || + new_embl_entry.getSequence().length() == 0) + { + // read failed + System.err.println("failed to read " + default_sequence_file_name + + "(given in options files)"); + return; + } + + getStatusLabel().setText(""); + + try + { + final Entry entry = new Entry(new_embl_entry); + + final EntryEdit new_entry_edit = makeEntryEdit(entry); + + new_entry_edit.setVisible(true); + + if(default_feature_file_name != null) + { + final Document feature_document = + DocumentFactory.makeDocument(default_feature_file_name); + + final uk.ac.sanger.artemis.io.Entry new_embl_table_entry = + EntryFileDialog.getEntryFromFile(this, + feature_document, + artemis_entry_information, + false); + + if(new_embl_table_entry == null) // open failed + return; + + final EntryGroup entry_group = new_entry_edit.getEntryGroup(); + + final Entry new_table_entry = + new Entry(entry.getBases(), new_embl_table_entry); + + entry_group.add(new_table_entry); + } + } catch(OutOfRangeException e) { + new MessageDialog(this, "read failed: one of the features in " + + default_feature_file_name + + " has an out of range location: " + + e.getMessage()); + } catch(NoSequenceException e) { + new MessageDialog(this, "read failed: " + + new_embl_entry.getName() + + " contains no sequence"); + } + } + } + + /** + * Make an EntryEdit component from the given Entry. + **/ + private EntryEdit makeEntryEdit(final Entry entry) + { + final Bases bases = entry.getBases(); + final EntryGroup entry_group = new SimpleEntryGroup(bases); + entry_group.add(entry); + final EntryEdit entry_edit = new EntryEdit(entry_group); + + return entry_edit; + } + + /** + * This method gets rid of an EntryEdit object and it's frame. Each object + * removed with this method must have been added previously with + * addEntryEdit(). + * @param entry_edit The object to get rid of. + **/ + public void entryEditFinished(EntryEdit entry_edit) + { + if(null == entry_edit) + throw new Error("entryEditFinished() was passed a null object"); + + if(!entry_edit_objects.removeElement(entry_edit)) + throw new Error("entryEditFinished() - could not remove a " + + "object from an empty vector"); + + entry_edit.setVisible(false); + entry_edit.dispose(); + } + + /** + * Add an EntryEdit object to our list of objects. + * @param entry_edit The object to add. + **/ + public synchronized void addEntryEdit(EntryEdit entry_edit) + { + entry_edit_objects.addElement(entry_edit); + } + + /** + * Read an Entry from the given EntrySource and make a new EntryEdit + * component for the Entry. + **/ + private void getEntryEditFromEntrySource(final EntrySource entry_source) + { + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + + SwingWorker entryWorker = new SwingWorker() + { + EntryEdit entry_edit; + public Object construct() + { + try + { + final Entry entry = entry_source.getEntry(true,progress_thread); + if(entry == null) + return null; + + final EntryGroup entry_group = + new SimpleEntryGroup(entry.getBases()); + + entry_group.add(entry); + entry_edit = new EntryEdit(entry_group); + } + catch(OutOfRangeException e) + { + new MessageDialog(ArtemisMain.this, "read failed: one of the features in " + + " the entry has an out of range " + + "location: " + e.getMessage()); + } + catch(NoSequenceException e) + { + new MessageDialog(ArtemisMain.this, "read failed: entry contains no sequence"); + } + catch(IOException e) + { + new MessageDialog(ArtemisMain.this, "read failed due to IO error: " + e); + } + return null; + } + + public void finished() + { + if(entry_edit != null) + entry_edit.setVisible(true); + if(progress_thread !=null) + progress_thread.finished(); + } + }; + entryWorker.start(); + } + + /** + * Force all the EntryEdit components to be redisplayed. + **/ + private void redisplayAll() + { + for(int i=0 ; i<entry_edit_objects.size() ; ++i) + entry_edit_objects.elementAt(i).redisplay(); + } + + /** + * Close the main frame and all EntryEdit frames and then this frame, + * then exit. + **/ + protected void exit() + { +// for(int i=0 ; i<entry_edit_objects.size() ;++i) +// entryEditFinished(entry_edit_objects.elementAt(i)); + +// if(filemanager != null) +// filemanager.setVisible(false); + +// setVisible(false); +// dispose(); +// System.gc(); + System.exit(0); + } + + /** + * Main entry point for the stand-alone version of Artemis. + **/ + public static void main(final String [] args) + { + final ArtemisMain main_window = new ArtemisMain(); + main_window.setVisible(true); + + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + + SwingWorker entryWorker = new SwingWorker() + { + public Object construct() + { + // read the entries given on the command line and in the diana.ini file + main_window.readArgsAndOptions(args,progress_thread); + return null; + } + + public void finished() + { + if(progress_thread !=null) + progress_thread.finished(); + } + }; + entryWorker.start(); + } + +} diff --git a/uk/ac/sanger/artemis/components/BasePlot.java b/uk/ac/sanger/artemis/components/BasePlot.java new file mode 100644 index 000000000..e71ccb918 --- /dev/null +++ b/uk/ac/sanger/artemis/components/BasePlot.java @@ -0,0 +1,627 @@ +/* BasePlot.java + * + * created: Tue Dec 15 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,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/BasePlot.java,v 1.1 2004-06-09 09:46:04 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.plot.*; +import uk.ac.sanger.artemis.util.OutOfRangeException; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A component for plotting functions over the base sequence. Scrolling and + * scale is tied to a FeatureDisplay component. + * + * @author Kim Rutherford + * @version $Id: BasePlot.java,v 1.1 2004-06-09 09:46:04 tjc Exp $ + **/ + +public class BasePlot extends Plot + implements DisplayAdjustmentListener, SelectionChangeListener { + /** + * Create a new FeatureDisplay object. + * @param algorithm The object that will generate the value we plot in + * this component. + * @param selection Used to set and display the current selection in the + * BasePlot. + * @param goto_event_source The object the we will call gotoBase () on. + * This allows the user to double click on a base in a BasePlot and have + * the FeatureDisplay follow. + **/ + public BasePlot (final BaseAlgorithm algorithm, + final Selection selection, + final GotoEventSource goto_event_source) { + super (algorithm, false); // false means don't draw the scale line + + this.selection = selection; + this.goto_event_source = goto_event_source; + this.bases = getBaseAlgorithm ().getBases (); + + getSelection ().addSelectionChangeListener (this); + + addPlotMouseListener (new PlotMouseListener () { + /** + * Set the selection to be a range from start_base to end_base. + **/ + private void setSelectionRange (final int start_base, + final int end_base) { + final Strand strand = bases.getForwardStrand (); + + try { + final MarkerRange marker_range = + strand.makeMarkerRangeFromPositions (start_base, end_base); + getSelection ().setMarkerRange (marker_range); + } catch (uk.ac.sanger.artemis.util.OutOfRangeException e) { + getSelection ().clear (); + } + } + + /** + * Called when the user clicks somewhere on the plot canvas. + * @param position the base/amino acid position of the click. This is + * -1 if and only if the click was outside the graph (eg. in the + * label at the top) + **/ + public void mouseClick (final int position) { + + } + + /** + * Called when the user drags the mouse over the plot. + * @param drag_start_position The base/amnino acid position where the + * drag started or -1 if the drag was started outside the graph. + * @param current_position the base/amino acid position of the click. + * This is -1 if and only if the user has dragged the mouse out of + * the graph (eg. in the label at the top) + **/ + public void mouseDrag (int drag_start_position, + int current_position) { + if (rev_comp_display) { + drag_start_position = + bases.getComplementPosition (drag_start_position); + current_position = + bases.getComplementPosition (current_position); + } + setSelectionRange (drag_start_position, + current_position); + } + + /** + * Called when the user double-clicks somewhere on the plot. + * @param position the base/amino acid position of the click. This is + * -1 if and only if the click was outside the graph (eg. in the + * label at the top) + **/ + public void mouseDoubleClick (int position) { + if (rev_comp_display) { + position = bases.getComplementPosition (position); + } + setSelectionRange (position, position); + getGotoEventSource ().gotoBase (position); + } + }); + } + + /** + * Used by getPreferredSize () and getMinimumSize (); + **/ + private final static int HEIGHT; + + static { + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + final Integer base_plot_height = + Options.getOptions ().getIntegerProperty ("base_plot_height"); + + if (base_plot_height == null) { + if (screen.height <= 600) { + HEIGHT = 100; + } else { + HEIGHT = 150; + } + } else { + HEIGHT = base_plot_height.intValue (); + } + } + + /** + * Overridden to set the component height to 150. + **/ + public Dimension getPreferredSize() { + return (new Dimension(getSize ().width, HEIGHT)); + } + + /** + * Overridden to set the component height to 150. + **/ + public Dimension getMinimumSize() { + return (new Dimension(getSize ().width, HEIGHT)); + } + + /** + * Implementation of the DisplayAdjustmentListener interface. Invoked when + * a component or changes the scale. + **/ + public void displayAdjustmentValueChanged (DisplayAdjustmentEvent event) { + start_base = event.getStart (); + end_base = event.getEnd (); + width_in_bases = event.getWidthInBases (); + rev_comp_display = event.isRevCompDisplay (); + recalculate_flag = true; + + if (event.getType () == DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT) { + selection_start_marker = null; + selection_end_marker = null; + } + + repaintCanvas (); + } + + /** + * We listen for SelectionChange events so that we can update the + * crosshairs. + **/ + public void selectionChanged (SelectionChangeEvent event) { + selection_start_marker = null; + selection_end_marker = null; + repaintCanvas (); + } + + /** + * Return the algorithm that was passed to the constructor. + **/ + public BaseAlgorithm getBaseAlgorithm () { + return (BaseAlgorithm) super.getAlgorithm (); + } + + /** + * Return the new start base to display, from the last event. + **/ + private int getStart () { + return start_base; + } + + /** + * Return the new end base to display, from the last event. + **/ + private int getEnd () { + return end_base; + } + + /** + * Return the width in bases of the display, from the last event. + **/ + private int getWidthInBases () { + return width_in_bases; + } + + /** + * This array is used by drawMultiValueGraph (). It is reallocated when + * the scale changes. + **/ + private float [][] value_array_array = null; + + /** + * The number of bases to step before each evaluation of the algorithm. + * (Set by recalculateValues ()). + **/ + private int step_size = 0; + + /** + * The maximum of the values in value_array_array. + **/ + private float min_value = Float.MAX_VALUE; + + /** + * The minimum of the values in value_array_array. + **/ + private float max_value = Float.MIN_VALUE; + + /** + * Recalculate the values in value_array_array, step_size, min_value and + * max_value. + **/ + protected void recalculateValues () { + final Float algorithm_minimum = getAlgorithm ().getMinimum (); + final Float algorithm_maximum = getAlgorithm ().getMaximum (); + + // use the Algorithm specified maximum if there is one - otherwise + // calculate it + if (algorithm_maximum == null) { + max_value = Float.MIN_VALUE; + } else { + max_value = algorithm_maximum.floatValue (); + } + + // use the Algorithm specified minimum if there is one - otherwise + // calculate it + if (algorithm_minimum == null) { + min_value = Float.MAX_VALUE; + } else { + min_value = algorithm_minimum.floatValue (); + } + + final int window_size = getWindowSize (); + + final Integer default_step_size = + getAlgorithm ().getDefaultStepSize (window_size); + + if (default_step_size == null) { + step_size = 1; + } else { + if (default_step_size.intValue () < window_size) { + step_size = default_step_size.intValue (); + } else { + step_size = window_size; + } + } + + int real_start = getStart (); + + if (real_start < 1) { + real_start = 1; + } + + final int unit_count = getEnd () - real_start; + + // the number of plot points in the graph + final int number_of_values = + (unit_count - (window_size - step_size)) / step_size; + + if (number_of_values < 2) { + // there is nothing to plot + value_array_array = null; + return; + } + + getBaseAlgorithm ().setRevCompDisplay (rev_comp_display); + + // the number of values that getValues () will return + final int get_values_return_count = + getBaseAlgorithm ().getValueCount (); + + if (value_array_array == null) { + value_array_array = new float [get_values_return_count][]; + } + + if (value_array_array[0] == null || + value_array_array[0].length != number_of_values) { + for (int i = 0 ; i < value_array_array.length ; ++i) { + value_array_array[i] = new float [number_of_values]; + } + } else { + // reuse the previous arrays + } + + float [] temp_values = new float [get_values_return_count]; + + for (int i = 0 ; i < number_of_values ; ++i) { + getBaseAlgorithm ().getValues (real_start + i * step_size, + real_start + i * step_size + + window_size - 1, + temp_values); + + for (int value_index = 0 ; + value_index < get_values_return_count ; + ++value_index) { + final float current_value = temp_values[value_index]; + + value_array_array[value_index][i] = current_value; + + // use the Algorithm specified maximum if there is one - otherwise + // calculate it + if (algorithm_maximum == null) { + if (current_value > max_value) { + max_value = current_value; + } + } + + // use the Algorithm specified minimum if there is one - otherwise + // calculate it + if (algorithm_minimum == null) { + if (current_value < min_value) { + min_value = current_value; + } + } + } + } + + recalculate_flag = false; + } + + /** + * Redraw the graph on the canvas using the algorithm, start_base and + * end_base. This method plots BaseWindowAlgorithm objects only. + * @param g The object to draw into. + **/ + public void drawMultiValueGraph (Graphics g) { + if (recalculate_flag) { + recalculateValues (); + } + + if (value_array_array == null) { + // there is nothing to draw - probably because the sequence is too short + drawMinMax (g, 0, 1); + + return; + } + + final int window_size = getWindowSize (); + + // the number of values to plot at each x position + final int get_values_return_count = + getBaseAlgorithm ().getValueCount (); + + final int number_of_values = value_array_array[0].length; + + if (number_of_values > 1) { + drawGlobalAverage (g, min_value, max_value); + } + + for (int value_index = 0 ; + value_index < get_values_return_count ; + ++value_index) { + if (get_values_return_count == 1) { + g.setColor (Color.black); + } else { + switch (value_index) { + case 0: + g.setColor (new Color (255, 0, 0)); + break; + case 1: + g.setColor (new Color (0, 200, 0)); + break; + case 2: + g.setColor (new Color (0, 0, 255)); + break; + default: + g.setColor (Color.black); + } + } + + final int offset; + + if (getStart () < 1) { + offset = 1 - getStart (); + } else { + offset = 0; + } + + drawPoints (g, min_value, max_value, step_size, window_size, + getWidthInBases (), + offset, + value_array_array[value_index]); + } + + drawMinMax (g, min_value, max_value); + + if (getCrossHairPosition () >= 0) { + final int cross_hair_position = getCrossHairPosition (); + + final int selection_base = getPointPosition (cross_hair_position); + + if (selection_base >= 1) { + if (selection_base > end_base) { + cancelCrossHairs (); + } else { + final String label_string; + + if (rev_comp_display) { + label_string = + String.valueOf (bases.getLength () - selection_base + 1); + } else { + label_string = String.valueOf (selection_base); + } + + drawCrossHair (g, cross_hair_position, label_string, 0); + } + } + } + + if (getCrossHairPosition () >= 0 && getSelectionStartMarker () != null) { + final int selection_first_base = + getSelectionStartMarker ().getRawPosition (); + + final String label_string; + + if (rev_comp_display) { + label_string = + String.valueOf (bases.getLength () - selection_first_base + 1); + } else { + label_string = String.valueOf (selection_first_base); + } + + if (Math.abs (selection_first_base - + getPointPosition (getCrossHairPosition ())) > 3) { + drawCrossHair (g, getCanvasPosition (selection_first_base), + label_string, 1); + } else { + // don't draw - too close to main cross hair + } + } + + if (getCrossHairPosition () >= 0 && getSelectionEndMarker () != null) { + if (getSelectionStartMarker () != null && + Math.abs ((getSelectionEndMarker ().getRawPosition () - + getSelectionStartMarker ().getRawPosition ())) >= 3) { + final int selection_last_base = + getSelectionEndMarker ().getRawPosition (); + + final String label_string; + + if (rev_comp_display) { + label_string = + String.valueOf (bases.getLength () - selection_last_base + 1); + } else { + label_string = String.valueOf (selection_last_base); + } + + if (Math.abs (selection_last_base - + getPointPosition (getCrossHairPosition ())) > 3) { + drawCrossHair (g, getCanvasPosition (selection_last_base), + label_string, 2); + } else { + // don't draw - too close to main cross hair + } + } + } + } + + /** + * Get the position in the Feature of the given canvas x position. This + * base position is the label used when the user clicks the mouse in on the + * canvas (see drawCrossHair ()). + **/ + protected int getPointPosition (final int canvas_x_position) { + return (int) ((1.0 * canvas_x_position / getCanvas ().getSize ().width) * + getWidthInBases ()) + getStart (); + } + + /** + * Return the canvas position of the given base, + **/ + private int getCanvasPosition (final int base) { + return (int) ((1.0 * base - getStart ()) / getWidthInBases () * + getCanvas ().getSize ().width); + } + + /** + * Return the Marker of the start base of the Selection or null if there is + * nothing selected. + **/ + private Marker getSelectionStartMarker () { + if (selection_start_marker == null) { + selection_start_marker = getSelection ().getLowestBaseOfSelection (); + + if (selection_start_marker != null && + rev_comp_display) { + final Strand strand = bases.getReverseStrand (); + final int orig_position = selection_start_marker.getRawPosition (); + final int rev_comp_position = + bases.getComplementPosition (orig_position); + try { + selection_start_marker = strand.makeMarker (orig_position); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + } + + return selection_start_marker; + } + + /** + * Return the Marker of the end base of the Selection or null if there is + * nothing selected. + **/ + private Marker getSelectionEndMarker () { + if (selection_end_marker == null) { + selection_end_marker = getSelection ().getHighestBaseOfSelection (); + + if (selection_end_marker != null && + rev_comp_display) { + final Strand strand = bases.getReverseStrand (); + final int orig_position = selection_end_marker.getRawPosition (); + final int rev_comp_position = + bases.getComplementPosition (orig_position); + try { + selection_end_marker = strand.makeMarker (orig_position); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + } + + return selection_end_marker; + } + + /** + * Return the Selection object that was passed to the constructor. + **/ + private Selection getSelection () { + return selection; + } + + /** + * Return the GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource getGotoEventSource () { + return goto_event_source; + } + + /** + * The start base to plot, as obtained from the DisplayAdjustmentEvent. + **/ + private int start_base; + + /** + * The end base to plot, as obtained from the DisplayAdjustmentEvent. + **/ + private int end_base; + + /** + * The width in bases of the display, as obtained from the + * DisplayAdjustmentEvent. + **/ + private int width_in_bases; + + /** + * True if and only if the FeatureDisplay is drawing in reverse complement + * mode. + **/ + private boolean rev_comp_display; + + /** + * The Bases that this BasePlot is graphing. + **/ + private Bases bases; + + /** + * The Marker of the start of the selection. This is a cache used by + * getSelectionStartMarker (). + **/ + private Marker selection_start_marker = null; + + /** + * The Marker of the end of the selection. This is a cache used by + * getSelectionEndMarker () + **/ + private Marker selection_end_marker = null; + + /** + * The Selection that was passed to the constructor. + **/ + private Selection selection; + + /** + * The GotoEventSource that was passed to the constructor. + **/ + private GotoEventSource goto_event_source; +} diff --git a/uk/ac/sanger/artemis/components/BasePlotGroup.java b/uk/ac/sanger/artemis/components/BasePlotGroup.java new file mode 100644 index 000000000..09a5bbaaf --- /dev/null +++ b/uk/ac/sanger/artemis/components/BasePlotGroup.java @@ -0,0 +1,337 @@ +/* BasePlotGroup.java + * + * created: Tue Dec 15 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/BasePlotGroup.java,v 1.1 2004-06-09 09:46:05 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.plot.*; + +import java.awt.*; +import java.awt.event.*; +import java.io.IOException; +import java.io.File; +import java.util.Vector; + +import javax.swing.*; + +/** + * This is a super-component containing several BasePlot components, each of + * which can toggled off and on. + * + * @author Kim Rutherford + * @version $Id: BasePlotGroup.java,v 1.1 2004-06-09 09:46:05 tjc Exp $ + **/ + +public class BasePlotGroup extends JPanel + implements DisplayAdjustmentListener { + /** + * Create a new BasePlotGroup component for the given EntryGroup. + * @param selection Used to set and display the current selection in the + * BasePlot. + * @param goto_event_source The object the we will call gotoBase () on. + * This allows the user to double click on a base in a BasePlot and have + * the FeatureDisplay follow. + **/ + public BasePlotGroup (final EntryGroup entry_group, + final Component owning_component, + final Selection selection, + final GotoEventSource goto_event_source) { + this.owning_component = owning_component; + this.entry_group = entry_group; + this.selection = selection; + this.goto_event_source = goto_event_source; + + final Strand forward_strand = + entry_group.getBases ().getForwardStrand (); + + final Strand reverse_strand = + entry_group.getBases ().getReverseStrand (); + + // the following arrays are from failed tests + final float [] [] test_weights = { + {1, 0,39,99,11}, // a + {76,8,15,1,45}, // c + {2,0,42,0,6}, // g + {21,91,4,0,38} // t + }; + + final float [] [] test_weights2 = { + {11,11,10, 8,11,10,11,11, 7, 8,25, 3,100, 0,27}, + {29,33,30,30,32,34,37,38,39,36,26,75, 0, 0,14}, + {14,12,10,10, 9,11,10, 9, 7, 6,26, 1, 0,100,49}, + {46,44,50,52,48,45,42,43,47,51,23,21, 0, 0,10} + }; + + final float [] [] test_weights3 = { + {0,0,1,0,0,0}, + {0,0,0,1,0,0}, + {1,0,0,0,1,0}, + {0,1,0,0,0,1}, + }; + + final float [] cai_test = { + 0.113F, 1.0F, 0.117F, 1.0F, + 1.0F, 0.693F, 0.036F, 0.005F, + 0.071F, 1.0F, 0.0F, 0.0F, + 1.0F, 0.077F, 0.0F, 1.0F, + + 0.006F, 0.003F, 0.039F, 0.003F, + 0.047F, 0.009F, 1.0F, 0.002F, + 0.245F, 1.0F, 1.0F, 0.007F, + 0.137F, 0.002F, 0.002F, 0.002F, + + 0.823F, 1.0F, 0.003F, 1.0F, + 0.921F, 1.0F, 0.012F, 0.006F, + 0.053F, 1.0F, 0.135F, 1.0F, + 0.021F, 0.031F, 1.0F, 0.003F, + + 1.0F, 0.831F, 0.002F, 0.018F, + 1.0F, 0.316F, 0.015F, 0.001F, + 0.554F, 1.0F, 1.0F, 0.016F, + 1.0F, 0.02F, 0.002F, 0.004F + }; + +// plot_algorithms_temp.add (new CAIWindowAlgorithm (forward_strand, cai_test)); +// plot_algorithms_temp.add (new UserBaseAlgorithm (forward_strand, "test", test_weights)); +// plot_algorithms_temp.add (new UserBaseAlgorithm (forward_strand, "test2", test_weights2)); +// plot_algorithms_temp.add (new UserBaseAlgorithm (forward_strand, "test3", test_weights3)); + + setLayout (gridbag); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.NORTH; + c.gridwidth = GridBagConstraints.REMAINDER; + c.gridheight = 1; + c.weightx = 1; + c.weighty = 0; + + c.insets = new Insets (0,0,5,0); + + addAlgorithm (new GCWindowAlgorithm (forward_strand)); + addAlgorithm (new GCSDWindowAlgorithm (forward_strand)); + addAlgorithm (new AGWindowAlgorithm (forward_strand)); + addAlgorithm (new GCFrameAlgorithm (forward_strand)); + addAlgorithm (new GCFrameAlgorithm (reverse_strand)); + addAlgorithm (new Codon12CorrelationAlgorithm(forward_strand)); + addAlgorithm (new Codon12CorrelationAlgorithm(reverse_strand)); + addAlgorithm (new GCDeviationAlgorithm (forward_strand)); + addAlgorithm (new ATDeviationAlgorithm (forward_strand)); + addAlgorithm (new KarlinSigAlgorithm (forward_strand)); + } + + /** + * Implementation of the DisplayAdjustmentListener interface. Invoked when + * a component (FeatureDisplay) scrolls or changes the scale. Sends the + * event to all the BasePlot components in this BasePlotGroup. + **/ + public void displayAdjustmentValueChanged (DisplayAdjustmentEvent event) { + final Component [] children = getComponents (); + + for (int i = 0 ; i < children.length ; ++i) { + if (children[i] instanceof BasePlot) { + ((BasePlot)children[i]).displayAdjustmentValueChanged (event); + } + } + } + + /** + * Add a new BasePlot component for the given algorithm. + * @return The new BasePlot + **/ + public BasePlot addAlgorithm (final BaseAlgorithm algorithm) { + plot_value_producers.addElement (algorithm); + + return makePlot (algorithm, gridbag, c); + } + + /** + * Return the first CodonUsageAlgorithm or null if there isn't one in the + * group. + **/ + public CodonUsageAlgorithm getCodonUsageAlgorithm () { + for (int i = 0 ; i < plot_value_producers.size () ; ++i) { + final BaseAlgorithm this_algorithm = + (BaseAlgorithm) plot_value_producers.elementAt (i); + + if (this_algorithm instanceof CodonUsageAlgorithm) { + return (CodonUsageAlgorithm) this_algorithm; + } + } + + return null; + } + + /** + * Return an array containing the Algorithm objects of the BasePlot + * components in this BasePlotGroup. + **/ + public BaseAlgorithm [] getPlotAlgorithms () { + final BaseAlgorithm [] return_array = + new BaseAlgorithm [plot_value_producers.size ()]; + + for (int i = 0 ; i < plot_value_producers.size () ; ++i) { + final BaseAlgorithm this_algorithm = + (BaseAlgorithm) plot_value_producers.elementAt (i); + return_array[i] = this_algorithm; + } + + return return_array; + } + + /** + * Return true if and only if the BasePlot for the given Algorithm is + * visible. + **/ + public boolean basePlotIsVisible (final Algorithm algorithm) { + final Component base_plot = findPlotByAlgorithm (algorithm); + + return base_plot.isVisible (); + } + + /** + * Given an Algorithm, find and set the visibility of the corresponding + * BasePlot component. + **/ + public void setVisibleByAlgorithm (final Algorithm algorithm, + final boolean visible) { + final JComponent base_plot = findPlotByAlgorithm (algorithm); + + base_plot.setVisible (visible); + if (getParent () != null) { + // XXX change to revalidate(). + getParent ().validate (); + } + } + + /** + * Find the BasePlot component in this BasePlotGroup object that is + * plotting the given Algorithm or null if no such BasePlot object exists. + **/ + private JComponent findPlotByAlgorithm (final Algorithm algorithm) { + final Component [] children = getComponents (); + + for (int i = 0 ; i < children.length ; ++i) { + if (children[i] instanceof BasePlot) { + final Algorithm component_algorithm = + ((BasePlot)children[i]).getAlgorithm (); + if (component_algorithm == algorithm) { + return (JComponent)children[i]; + } + } + } + + return null; + } + + /** + * Create a Plot component for the given Algorithm and then make a button + * for it so that it can be shown and hidden. Note that button making is + * currently disabled + * @param algorithm The Algorithm to create a Plot of. + * @param gridbag The GridBagLayout to use to lay out the Plot components. + * @param constraints The GridBagConstraints object to use to lay out the + * Plot components. + * @return The new BasePlot + **/ + private BasePlot makePlot (BaseAlgorithm algorithm, + GridBagLayout gridbag, + GridBagConstraints constraints) { + + final BasePlot new_base_plot = + new BasePlot (algorithm, getSelection (), getGotoEventSource ()); + + gridbag.setConstraints (new_base_plot, constraints); + add (new_base_plot); + new_base_plot.setVisible (false); + + getSelection ().addSelectionChangeListener (new_base_plot); + + if (getParent () != null) { + // XXX change to revalidate(). + getParent ().validate (); + } + + return new_base_plot; + } + + /** + * Return the Selection object that was passed to the constructor. + **/ + private Selection getSelection () { + return selection; + } + + /** + * Return the EntryGroup object that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + /** + * Return the GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource getGotoEventSource () { + return goto_event_source; + } + + /** + * The EntryGroup that contains the sequence that this JComponent is + * displaying. + **/ + private final EntryGroup entry_group; + + /** + * This array contains the Algorithm objects of the BasePlot components in + * this BasePlotGroup, as set by the constructor. + **/ + private final Vector plot_value_producers = new Vector (); + + /** + * The layout object used by this component. + **/ + private final GridBagLayout gridbag = new GridBagLayout(); + + /** + * The constraints object used by this component. + **/ + private final GridBagConstraints c = new GridBagConstraints(); + + /** + * The Selection that was passed to the constructor. + **/ + private Selection selection; + + /** + * The GotoEventSource that was passed to the constructor. + **/ + private GotoEventSource goto_event_source; + + /** + * This is a reference to the parent Container of this BasePlot object. + **/ + private Component owning_component; +} diff --git a/uk/ac/sanger/artemis/components/BioJavaEntrySource.java b/uk/ac/sanger/artemis/components/BioJavaEntrySource.java new file mode 100644 index 000000000..8f9a64ce7 --- /dev/null +++ b/uk/ac/sanger/artemis/components/BioJavaEntrySource.java @@ -0,0 +1,223 @@ +/* BioJavaEntrySource.java + * + * created: Tue Apr 10 2001 + * + * This file is part of Artemis + * + * Copyright (C) 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/BioJavaEntrySource.java,v 1.1 2004-06-09 09:46:06 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.net.*; +import java.io.*; +import java.util.*; +import java.lang.RuntimeException; + +import org.biojava.bio.program.das.*; +import org.biojava.bio.BioException; +import org.biojava.bio.seq.io.SequenceBuilderFactory; +import org.biojava.bio.seq.io.EmblProcessor; +import org.biojava.bio.seq.io.SmartSequenceBuilder; +import org.biojava.bio.seq.io.SymbolTokenization; +import org.biojava.bio.seq.io.StreamReader; +import org.biojava.bio.seq.io.SequenceFormat; +import org.biojava.bio.seq.io.EmblLikeFormat; +import org.biojava.bio.seq.SequenceIterator; +import org.biojava.bio.symbol.Alphabet; +import org.biojava.bio.seq.DNATools; + +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.BioJavaEntry; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +/** + * This is an EntrySource that reads Entry objects from a BioJava Sequence + * object. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: BioJavaEntrySource.java,v 1.1 2004-06-09 09:46:06 tjc Exp $ + **/ + +public class BioJavaEntrySource implements EntrySource +{ + /** + * Create a new BioJavaEntrySource. + **/ + public BioJavaEntrySource () { + + } + + /** + * Get an Entry object from this source (by reading from a file, reading + * from a CORBA server, or whatever). + * @param bases The Bases object to pass to the Entry constructor. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the read is cancelled by the user or if the + * read fails. + **/ + public Entry getEntry (final Bases bases, final ProgressThread progress_thread, + final boolean show_progress) + throws OutOfRangeException, IOException + { + final String fileName = "/nfs/team81/kmr/pow/java2/AB000095.embl"; + final FileDocument document = new FileDocument (new File (fileName)); + + final BioJavaEntry emblEntry = + new BioJavaEntry (document, new EmblLikeFormat ()); + + return new Entry (bases, emblEntry); + } + + /** + * Get an Entry object from this source (by reading from a file, reading + * from a CORBA server, or whatever). + * @param bases The Bases object to pass to the Entry constructor. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the read is cancelled by the user or if the + * read fails. + **/ + public Entry getEntry (final Bases bases, final boolean show_progress) + throws OutOfRangeException, IOException + { + return getEntry(bases, null, show_progress); + } + + /** + * Get an Entry object from this source (by reading from a file, reading + * from a CORBA server, or whatever). A Bases object will be created for + * the sequence of the new Entry. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry(final boolean show_progress, + final ProgressThread progress_thread) + throws OutOfRangeException, NoSequenceException, IOException + { + return getEntry(show_progress); + } + + /** + * Get an Entry object from this source (by reading from a file, reading + * from a CORBA server, or whatever). A Bases object will be created for + * the sequence of the new Entry. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final boolean show_progress) + throws OutOfRangeException, NoSequenceException, IOException + { +// DASSequence dasSeq = null; + +// try { +// final URL dbURL = +// new URL("http://genome.ornl.gov/das/das.cgi/ecoli"); +// // new URL("http://genome.cse.ucsc.edu:80/cgi-bin/das/cb1"); +// // new URL("http://servlet.sanger.ac.uk:8080/das/ens1131cds/"); +// final DASSequenceDB dasDB = new DASSequenceDB (dbURL); + +// final Set ids = dasDB.ids(); + +// for (Iterator i = ids.iterator(); i.hasNext(); ) { +// System.out.println(i.next().toString()); +// } + +// // dasSeq = (DASSequence) dasDB.getSequence((String) ids.iterator().next()); + +// SequenceIterator si = dasDB.sequenceIterator(); + +// if (si.hasNext()) { +// dasSeq = (DASSequence) si.nextSequence(); + +// System.err.println("Got: " + dasSeq); + +// final BioJavaEntry bioJavaEntry = new BioJavaEntry (dasSeq); + +// return new Entry (bioJavaEntry); +// } else { +// System.err.println("No more sequences..."); +// } + + +// // final BioJavaEntry emblEntry = +// // new BioJavaEntry (document, new EmblLikeFormat ()); + +// // return new Entry (emblEntry); +// } catch (MalformedURLException mue) { +// mue.printStackTrace(); +// } catch (BioException be) { +// be.printStackTrace(); +// } + +// return null; + + final String fileName = "/nfs/team81/kmr/pow/java2/AE002734.game"; + final FileDocument document = new FileDocument (new File (fileName)); + + final BioJavaEntry emblEntry = + new BioJavaEntry (document, new EmblLikeFormat ()); + + return new Entry (emblEntry); + +// final String fileName = "/nfs/team81/kmr/pow/java2/AB000095.embl"; +// final FileDocument document = new FileDocument (new File (fileName)); + +// final BioJavaEntry emblEntry = +// new BioJavaEntry (document, new EmblLikeFormat ()); + +// return new Entry (emblEntry); + } + + /** + * Returns true if and only if this EntrySource always returns "full" + * entries. ie. entries that contain features and sequence. + **/ + public boolean isFullEntrySource () { + return true; + } + + /** + * Return the name of this source (for display to the user in menus). + **/ + public String getSourceName () { + return "BioJava"; + } +} diff --git a/uk/ac/sanger/artemis/components/CanvasPanel.java b/uk/ac/sanger/artemis/components/CanvasPanel.java new file mode 100644 index 000000000..cd0c1386a --- /dev/null +++ b/uk/ac/sanger/artemis/components/CanvasPanel.java @@ -0,0 +1,240 @@ +/* CanvasPanel.java + * + * created: Sat Jun 17 2000 + * + * This file is part of Artemis + * + * Copyright(C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or(at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/CanvasPanel.java,v 1.1 2004-06-09 09:46:07 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** + * This is a JPanel that contains a JPanel containing a JComponent. Both Panels + * have BorderLayout. The JComponent is added at "Center". + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: CanvasPanel.java,v 1.1 2004-06-09 09:46:07 tjc Exp $ + **/ + +abstract public class CanvasPanel extends JPanel +{ + + /** + * Off screen image used for double buffering when drawing the canvas. + **/ + private Image offscreen; + + /** Contains the canvas. */ + private JPanel mid_panel = null; + + /** The drawing area for this component. */ + private JComponent canvas = null; + + /** The height of the font used in this component. */ + private int font_ascent; + + /** maximum height of the font used in this component. */ + private int font_max_ascent; + + /** descent of the font used in this component. */ + private int font_descent; + + /** The(maximum) width of the font used in this component. */ + private int font_width; + + /** base line of the font used in this component. */ + private int font_base_line; + + /** + * Create a new JPanel(mid_panel) and a JComponent. + **/ + public CanvasPanel() + { + setLayout(new BorderLayout()); + setFontInfo(); + createCanvas(); + } + + /** + * Call repaint() on the canvas object. + **/ + protected void repaintCanvas() + { + getCanvas().repaint(); + } + + /** + * Create a JPanel(mid_panel) and a JComponent object. + **/ + private void createCanvas() + { + mid_panel = new JPanel(); + mid_panel.setLayout(new BorderLayout()); + + canvas = new JComponent() + { + /** + * Set the offscreen buffer to null as part of invalidation. + **/ + public void invalidate() + { + super.invalidate(); + offscreen = null; + } + + /** + * Override update to *not* erase the background before painting + */ + public void update(final Graphics g) + { + paint(g); + } + + /** + * Paint the canvas. + */ + public void paint(final Graphics g) + { + final int canvas_width = canvas.getSize().width; + final int canvas_height = canvas.getSize().height; + + // there is no point drawing into a zero width canvas + if(canvas_height <= 0 || canvas_width <= 0) + return; + + if(!canvas.isVisible()) + return; + + if(offscreen == null) + offscreen = canvas.createImage(canvas_width, canvas_height); + + Graphics og = offscreen.getGraphics(); + og.setClip(0, 0, canvas_width, canvas_height); + + paintCanvas(og); + g.drawImage(offscreen, 0, 0, null); + g.dispose(); + } + }; + + mid_panel.add(canvas, "Center"); + add(mid_panel, "Center"); + } + + /** + * Return the JComponent that was created by createCanvas(). + **/ + protected JComponent getCanvas() + { + return canvas; + } + + /** + * Returns the sub-JPanel that contains the JComponent. + **/ + protected JPanel getMidPanel() + { + return mid_panel; + } + + /** + * Called by canvas.paint() when + **/ + abstract protected void paintCanvas(final Graphics graphics); + + /** + * Set font_width and font_ascent from the default font. + **/ + private void setFontInfo() + { + FontMetrics fm = getFontMetrics(getFont()); + + // find the width of a wide character + font_width = fm.charWidth('M'); + font_ascent = fm.getAscent(); + font_max_ascent = fm.getMaxAscent(); + font_descent = fm.getDescent(); + } + + /** + * Return the width of the canvas. + **/ + public int getCanvasWidth() + { + return getCanvas().getSize().width; + } + + /** + * Return the height of the canvas. + **/ + public int getCanvasHeight() + { + return getCanvas().getSize().height; + } + + /** + * Return the width of our font, as calculated by setFontInfo(). + **/ + public int getFontWidth() + { + return font_width; + } + + /** + * Return the ascent(height above the baseline) of our font, as calculated + * by setFontInfo(). + **/ + public int getFontAscent() + { + return font_ascent; + } + + /** + * Return the max ascent(height above the baseline) of our font, as + * calculated by setFontInfo(). + **/ + public int getFontMaxAscent() + { + return font_ascent; + } + + /** + * Return the descent of our font, as calculated by setFontInfo(). + **/ + public int getFontDescent() + { + return font_descent; + } + + /** + * The max ascent + descent of the default font. + **/ + public int getFontHeight() + { + return getFontMaxAscent() + getFontDescent(); + } + +} diff --git a/uk/ac/sanger/artemis/components/ChoiceFrame.java b/uk/ac/sanger/artemis/components/ChoiceFrame.java new file mode 100644 index 000000000..e63fba0ed --- /dev/null +++ b/uk/ac/sanger/artemis/components/ChoiceFrame.java @@ -0,0 +1,111 @@ +/* ChoiceFrame.java + * + * created: Tue Aug 6 2002 + * + * This file is part of Artemis + * + * Copyright (C) 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/ChoiceFrame.java,v 1.1 2004-06-09 09:46:08 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.util.StringVector; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A Choice in a JFrame. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ChoiceFrame.java,v 1.1 2004-06-09 09:46:08 tjc Exp $ + **/ + +public class ChoiceFrame extends JFrame { + /** + * Create a new ChoiceFrame component with the given list of Strings. + **/ + public ChoiceFrame (final String choice_title, final StringVector strings) { + super (choice_title); + + choice = new JComboBox (); + + for (int i = 0 ; i < strings.size () ; ++i) { + choice.addItem (strings.elementAt (i)); + } + + final JPanel choice_panel = new JPanel (); + choice_panel.add (choice); + + getContentPane ().add (choice_panel, "Center"); + + final JPanel panel = new JPanel (); + + panel.add (ok_button); + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + ChoiceFrame.this.dispose (); + } + }); + + panel.add (close_button); + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + ChoiceFrame.this.dispose (); + } + }); + + getContentPane ().add (panel, "South"); + pack (); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + ChoiceFrame.this.dispose (); + } + }); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + } + + /** + * Return the Choice component that is displayed in this JFrame. + **/ + public JComboBox getChoice () { + return choice; + } + + /** + * Return the reference of the OK button of this Chooser. + **/ + public JButton getOKButton () { + return ok_button; + } + + private JComboBox choice; + + final private JButton ok_button = new JButton ("OK"); + + final private JButton close_button = new JButton ("Cancel"); +} diff --git a/uk/ac/sanger/artemis/components/ComparatorDialog.java b/uk/ac/sanger/artemis/components/ComparatorDialog.java new file mode 100644 index 000000000..6b02d052e --- /dev/null +++ b/uk/ac/sanger/artemis/components/ComparatorDialog.java @@ -0,0 +1,331 @@ +/* ComparatorDialog.java + * + * created: Wed May 10 2000 + * + * This file is part of Artemis + * + * Copyright(C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or(at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ComparatorDialog.java,v 1.1 2004-06-09 09:46:09 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.util.InputStreamProgressListener; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * ComparatorDialog a dialog that allows the user to the choose two files to + * compare with a Comparator component and a file containing comparison data. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ComparatorDialog.java,v 1.1 2004-06-09 09:46:09 tjc Exp $ + **/ + +public class ComparatorDialog extends JFrame +{ + /** + * Create a dialog that allow the user to the choose two files to compare + * and a file containing comparison data. + * @param ActMain the object to call makeMultiComparator() on + **/ + public ComparatorDialog(final ActMain act_main) + { + final JPanel top_panel = new JPanel(); + + getContentPane().add(top_panel, "Center"); + + final GridBagLayout gridbag = new GridBagLayout(); + top_panel.setLayout(gridbag); + + final Vector text_field_vector = new Vector(); + + for(int i = 0; i < 3; ++i) + { + final String label; + switch(i) + { + case 0: + label = "Sequence file 1"; + break; + case 1: + label = "Comparison file 1"; + break; + case 2: + label = "Sequence file 2"; + break; + default: + throw new Error("internal error"); + } + + final JTextField text_field = + makeFileNamePanel(label, top_panel, gridbag); + + text_field_vector.addElement(text_field); + } + + + final GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.NORTH; + c.weighty = 0; + c.gridwidth = 1; + + final JButton more_button = new JButton("more files ..."); + + final JPanel more_button_panel = new JPanel(); + more_button_panel.setLayout(new FlowLayout(FlowLayout.LEFT)); + more_button_panel.add(more_button); + + c.gridwidth = 1; + gridbag.setConstraints(more_button_panel, c); + top_panel.add(more_button_panel); + + more_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + top_panel.remove(more_button_panel); + + final boolean add_sequence_flag; + + if(text_field_vector.size() % 2 == 0 || + Options.getOptions().isNoddyMode()) + add_sequence_flag = true; + else + add_sequence_flag = false; + + if(text_field_vector.size() % 2 == 1 || + Options.getOptions().isNoddyMode()) + { + final String seq_label = + "Comparison file " +(text_field_vector.size() / 2 + 1); + final JTextField seq_text_field = + makeFileNamePanel(seq_label, top_panel, gridbag); + + text_field_vector.addElement(seq_text_field); + } + + if(add_sequence_flag) + { + final String comp_label = + "Sequence file " +(text_field_vector.size() / 2 + 1); + final JTextField comp_text_field = + makeFileNamePanel(comp_label, top_panel, gridbag); + + text_field_vector.addElement(comp_text_field); + } + + top_panel.add(more_button_panel); + + packAndCentre(); + } + }); + + + final JButton apply_button = new JButton("Apply"); + + apply_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + doApply(act_main, text_field_vector); + } + }); + + + final JButton close_button = new JButton("Close"); + + close_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + ComparatorDialog.this.dispose(); + } + }); + + + final FlowLayout flow_layout = + new FlowLayout(FlowLayout.CENTER, 15, 5); + + final JPanel close_and_apply_panel = new JPanel(flow_layout); + + close_and_apply_panel.add(apply_button); + close_and_apply_panel.add(close_button); + + getContentPane().add(close_and_apply_panel, "South"); + + + addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent event) + { + ComparatorDialog.this.dispose(); + } + }); + + packAndCentre(); + } + + /** + * Call pack() and then centre the JFrame in the middle of the screen. + **/ + private void packAndCentre() + { + pack(); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation(new Point((screen.width - getSize().width) / 2, + (screen.height - getSize().height) / 2)); + } + + /** + * Make a panel for choosing one file name panel. + **/ + private JTextField makeFileNamePanel(final String label_string, + final JPanel parent_panel, + final GridBagLayout gridbag) + { + GridBagConstraints c = new GridBagConstraints(); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.NORTH; + c.weighty = 0; + + final JLabel label = new JLabel(label_string); + + final JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT)); + panel.add(label); + + c.gridwidth = 1; + gridbag.setConstraints(panel, c); + parent_panel.add(panel); + + final TextFieldSink text_field = new TextFieldSink("", 28); + gridbag.setConstraints(text_field, c); + parent_panel.add(text_field); + + final JButton choose_button = new JButton("Choose ..."); + + choose_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent _) + { + final StickyFileChooser file_dialog = + new StickyFileChooser(); + + file_dialog.setDialogTitle("Choose first sequence ..."); + file_dialog.setFileSelectionMode(JFileChooser.FILES_ONLY); + file_dialog.setDialogType(JFileChooser.OPEN_DIALOG); + + file_dialog.showOpenDialog(ComparatorDialog.this); + + if(file_dialog.getSelectedFile() != null) + { + final File selected_file = + new File(file_dialog.getCurrentDirectory(), + file_dialog.getSelectedFile().getName()); + + text_field.setText(selected_file.toString()); + } + } + }); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(choose_button, c); + parent_panel.add(choose_button); + + return text_field; + } + + /** + * Attempt to call ActMain.makeComparator() for the given file names. + * @param ActMain the object to call makeMultiComparator() on + **/ + private void doApply(final ActMain act_main, + final Vector text_field_vector) + { + if(text_field_vector.size() < 3) + throw new Error("internal error - not enough file names given to " + + "ComparatorDialog.doApply()"); + + String [] file_names = new String [text_field_vector.size()]; + + for(int i = 0; i < text_field_vector.size(); ++i) + { + file_names[i] = + ((JTextField) text_field_vector.elementAt(i)).getText().trim(); + + if(file_names[i].length() == 0) + { + // ignore the problem if there are at least 3 files listed(ie. seq1 + // comp1v2 seq2) and the remaining files lengths are zero + if(i > 3) + { + // set to true if a text field is found that isn't zero length + boolean found_file = false; + + for(int sub_index = i; sub_index < text_field_vector.size(); + ++sub_index) + { + final JTextField text_field = + (JTextField) text_field_vector.elementAt(sub_index); + + final String this_text = text_field.getText().trim(); + + if(this_text.length() != 0) + found_file = true; + } + + if(!found_file) + { + // truncate file_names + final String [] new_file_names = new String [i]; + System.arraycopy(file_names, 0, new_file_names, 0, i); + file_names = new_file_names; + break; + } + } + + new MessageDialog(this, "one of the file names is missing"); + return; + } + } + + final MessageFrame reading_message = new MessageFrame("reading ..."); + + final InputStreamProgressListener progress_listener = + act_main.getInputStreamProgressListener(); + + if(ActMain.makeMultiComparator(act_main, progress_listener, + file_names)) + ComparatorDialog.this.dispose(); + + reading_message.dispose(); + } +} diff --git a/uk/ac/sanger/artemis/components/ComparatorGlue.java b/uk/ac/sanger/artemis/components/ComparatorGlue.java new file mode 100644 index 000000000..8da6a0ab9 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ComparatorGlue.java @@ -0,0 +1,464 @@ +/* ComparatorGlue.java + * + * created: Tue Sep 11 2001 + * + * This file is part of Artemis + * + * Copyright (C) 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/ComparatorGlue.java,v 1.1 2004-06-09 09:46:10 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.ReadOnlyException; +import uk.ac.sanger.artemis.util.OutOfRangeException; + +import java.util.*; +import java.awt.*; + +import javax.swing.*; + +/** + * This class contains the Event glue needed to combine two FeatureDisplays + * and an AlignmentViewer. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ComparatorGlue.java,v 1.1 2004-06-09 09:46:10 tjc Exp $ + **/ + +public class ComparatorGlue { + /** + * Create a new ComparatorGlue object that glues the given components + * togeather. + **/ + public ComparatorGlue (final JFrame parent_frame, + final FeatureDisplay subject_feature_display, + final FeatureDisplay query_feature_display, + final AlignmentViewer alignment_viewer) { + this.parent_frame = parent_frame; + this.subject_feature_display = subject_feature_display; + this.query_feature_display = query_feature_display; + this.alignment_viewer = alignment_viewer; + + getSubjectEntryGroup ().addFeatureChangeListener (getSubjectSelection ()); + getQueryEntryGroup ().addEntryChangeListener (getQuerySelection ()); + + addDisplayListeners (subject_feature_display, query_feature_display); + + orig_subject_forward_strand = + getSubjectEntryGroup ().getBases ().getForwardStrand (); + orig_query_forward_strand = + getQueryEntryGroup ().getBases ().getForwardStrand (); + + makeAlignmentEventListener (); + } + + /** + * Wire-up the two FeatureDisplay objects with DisplayAdjustmentListeners. + **/ + private void addDisplayListeners (final FeatureDisplay subject_display, + final FeatureDisplay query_display) { + + subject_listener = new DisplayAdjustmentListener () { + public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) { + if (e.getType () == DisplayAdjustmentEvent.REV_COMP_EVENT) { + getAlignmentViewer ().unlockDisplays (); + return; + } + + subject_display.removeDisplayAdjustmentListener (subject_listener); + query_display.removeDisplayAdjustmentListener (query_listener); + + query_display.setScaleFactor (e.getScaleFactor ()); + + if (getAlignmentViewer ().displaysAreLocked () && + (e.getType () == DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT || + e.getType () == DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT)) { + final int difference = e.getStart () - subject_first_base_position; + + query_first_base_position = + query_display.getForwardBaseAtLeftEdge () + difference; + + query_display.setFirstBase (query_first_base_position); + } + + subject_first_base_position = e.getStart (); + + subject_display.addDisplayAdjustmentListener (subject_listener); + query_display.addDisplayAdjustmentListener (query_listener); + } + }; + + query_listener = new DisplayAdjustmentListener () { + public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) { + if (e.getType () == DisplayAdjustmentEvent.REV_COMP_EVENT) { + getAlignmentViewer ().unlockDisplays (); + return; + } + + subject_display.removeDisplayAdjustmentListener (subject_listener); + query_display.removeDisplayAdjustmentListener (query_listener); + + subject_display.setScaleFactor (e.getScaleFactor ()); + + if (getAlignmentViewer ().displaysAreLocked () && + (e.getType () == DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT || + e.getType () == DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT)) { + final int difference = e.getStart () - query_first_base_position; + + subject_first_base_position = + subject_display.getForwardBaseAtLeftEdge () + difference; + + subject_display.setFirstBase (subject_first_base_position); + } + + query_first_base_position = e.getStart (); + + subject_display.addDisplayAdjustmentListener (subject_listener); + query_display.addDisplayAdjustmentListener (query_listener); + } + }; + + subject_display.addDisplayAdjustmentListener (subject_listener); + query_display.addDisplayAdjustmentListener (query_listener); + + final DisplayAdjustmentListener subject_align_listener = + new DisplayAdjustmentListener () { + public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) { + getAlignmentViewer ().setSubjectSequencePosition (e); + } + }; + + subject_display.addDisplayAdjustmentListener (subject_align_listener); + + final DisplayAdjustmentListener query_align_listener = + new DisplayAdjustmentListener () { + public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) { + getAlignmentViewer ().setQuerySequencePosition (e); + } + }; + + query_display.addDisplayAdjustmentListener (query_align_listener); + } + + /** + * Make an AlignmentListener which calls alignAt (). + **/ + private void makeAlignmentEventListener () { + final AlignmentListener listener = + new AlignmentListener () { + public void alignMatchChosen (AlignmentEvent e) { + alignAt (e.getMatch ()); + } + }; + + getAlignmentViewer ().addAlignmentListener (listener); + } + + /** + * Scroll both the subject and query so that they centre on the given + * AlignMatch. + **/ + private void alignAt (final AlignMatch align_match) { + getAlignmentViewer ().unlockDisplays (); + getAlignmentViewer ().disableSelection (); + + maybeRevCompQuery (align_match); + + final int subject_length = getSubjectForwardStrand ().getSequenceLength (); + final int query_length = getQueryForwardStrand ().getSequenceLength (); + + int subject_sequence_start = + AlignmentViewer.getRealSubjectSequenceStart (align_match, + subject_length, + getAlignmentViewer ().subjectIsRevComp ()); + int subject_sequence_end = + AlignmentViewer.getRealSubjectSequenceEnd (align_match, + subject_length, + getAlignmentViewer ().subjectIsRevComp ()); + int query_sequence_start = + AlignmentViewer.getRealQuerySequenceStart (align_match, + query_length, + getAlignmentViewer ().queryIsRevComp ()); + int query_sequence_end = + AlignmentViewer.getRealQuerySequenceEnd (align_match, + query_length, + getAlignmentViewer ().queryIsRevComp ()); + + if (getSubjectDisplay ().isRevCompDisplay ()) { + subject_sequence_start = + getSubjectDisplay ().getSequenceLength () - subject_sequence_start + 1; + subject_sequence_end = + getSubjectDisplay ().getSequenceLength () - subject_sequence_end + 1; + } + + if (getQueryDisplay ().isRevCompDisplay ()) { + query_sequence_start = + getQueryDisplay ().getSequenceLength () - query_sequence_start + 1; + query_sequence_end = + getQueryDisplay ().getSequenceLength () - query_sequence_end + 1; + } + + final int new_subject_base = + subject_sequence_start + + (subject_sequence_end - subject_sequence_start) / 2; + getSubjectDisplay ().makeBaseVisible (new_subject_base); + + try { + final Strand subject_strand; + + if (align_match.isRevMatch () ^ + !getOrigSubjectForwardStrand ().isForwardStrand ()) { + subject_strand = getSubjectReverseStrand (); + } else { + subject_strand = getSubjectForwardStrand (); + } + + final MarkerRange new_subject_marker = + subject_strand.makeMarkerRangeFromRawPositions (subject_sequence_start, + subject_sequence_end); + getSubjectSelection ().setMarkerRange (new_subject_marker); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + final int new_query_base = + query_sequence_start + + (query_sequence_end - query_sequence_start) / 2; + getQueryDisplay ().makeBaseVisible (new_query_base); + + try { + final Strand query_strand; + + if (getOrigQueryForwardStrand ().isForwardStrand ()) { + query_strand = getQueryForwardStrand (); + } else { + query_strand = getQueryReverseStrand (); + } + + final MarkerRange new_query_marker_range = + query_strand.makeMarkerRangeFromRawPositions (query_sequence_start, + query_sequence_end); + + getQuerySelection ().setMarkerRange (new_query_marker_range); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + getAlignmentViewer ().lockDisplays (); + getAlignmentViewer ().enableSelection (); + } + + /** + * Called by alignAt () to reverse and complement the query sequence if the + * given AlignMatch is currently a match from the subject sequence to the + * reverse complement of the query sequence. + **/ + private void maybeRevCompQuery (final AlignMatch align_match) { + if (!getAlignmentViewer ().offerToFlip ()) { + return; + } + + // true if and only if exactly one of the query and subject is rev-comped + final boolean display_is_rev_comped; + + if (getOrigSubjectForwardStrand ().isForwardStrand () ^ + getOrigQueryForwardStrand ().isForwardStrand () ^ + getSubjectDisplay ().isRevCompDisplay () ^ + getQueryDisplay ().isRevCompDisplay ()) { + display_is_rev_comped = true; + } else { + display_is_rev_comped = false; + } + + if (align_match.isRevMatch () ^ display_is_rev_comped) { + final YesNoDialog yes_no_dialog = + new YesNoDialog (parent_frame, + "reverse and complement query sequence display?"); + if (yes_no_dialog.getResult ()) { + if (getQueryDisplay ().isRevCompDisplay ()) { + getQueryDisplay ().setRevCompDisplay (false); + } else { + getQueryDisplay ().setRevCompDisplay (true); + } + } + } + } + + /** + * Send an event to those object listening for it. + * @param listeners A Vector of the objects that the event should be sent + * to. + * @param event The event to send + **/ + private void fireAction (final Vector listeners, final EventObject event) { + final Vector targets; + // copied from a book - synchronising the whole method might cause a + // deadlock + synchronized (this) { + targets = (Vector) listeners.clone (); + } + + for (int i = 0 ; i < targets.size () ; ++i) { + GotoListener target = (GotoListener) targets.elementAt (i); + + if (event instanceof GotoEvent) { + final GotoListener goto_listener = (GotoListener) target; + goto_listener.performGoto ((GotoEvent) event); + } else { + throw new Error ("EntryEdit.fireAction () - unknown event"); + } + } + } + + /** + * Return the forward Strand of the subject EntryGroup from when the + * Comparator was created. + **/ + public Strand getOrigSubjectForwardStrand () { + return orig_subject_forward_strand; + } + + /** + * Return the forward Strand of the query EntryGroup from when the + * Comparator was created. + **/ + public Strand getOrigQueryForwardStrand () { + return orig_query_forward_strand; + } + + /** + * Return the GotoEventSource object used by the subject FeatureDisplay. + **/ + public GotoEventSource getSubjectGotoEventSource () { + return getSubjectDisplay ().getGotoEventSource (); + } + + /** + * Return the GotoEventSource object used by the query FeatureDisplay. + **/ + public GotoEventSource getQueryGotoEventSource () { + return getQueryDisplay ().getGotoEventSource (); + } + + /** + * Return the reference of the EntryGroup in view in the subject + * FeatureDisplay. + **/ + public EntryGroup getSubjectEntryGroup () { + return getSubjectDisplay ().getEntryGroup (); + } + + /** + * Return the reference of the EntryGroup in view in the query + * FeatureDisplay. + **/ + public EntryGroup getQueryEntryGroup () { + return getQueryDisplay ().getEntryGroup (); + } + + /** + * Return the Selection object used by the subject FeatureDisplay. + **/ + public Selection getSubjectSelection () { + return getSubjectDisplay ().getSelection (); + } + + /** + * Return the Selection object used by the query FeatureDisplay. + **/ + public Selection getQuerySelection () { + return getQueryDisplay ().getSelection (); + } + + /** + * Return the AlignmentViewer that was created in the constructor. + **/ + private AlignmentViewer getAlignmentViewer () { + return alignment_viewer; + } + + /** + * Return the reference of the subject FeatureDisplay. + **/ + public FeatureDisplay getSubjectDisplay () { + return subject_feature_display; + } + + /** + * Return the reference of the query FeatureDisplay. + **/ + public FeatureDisplay getQueryDisplay () { + return query_feature_display; + } + + /** + * Return the current forward Strand of the subject EntryGroup. + **/ + private Strand getSubjectForwardStrand () { + return getSubjectEntryGroup ().getBases ().getForwardStrand (); + } + + /** + * Return the current forward Strand of the query EntryGroup. + **/ + private Strand getQueryForwardStrand () { + return getQueryEntryGroup ().getBases ().getForwardStrand (); + } + + /** + * Return the current reverse Strand of the subject EntryGroup. + **/ + private Strand getSubjectReverseStrand () { + return getSubjectEntryGroup ().getBases ().getReverseStrand (); + } + + /** + * Return the current reverse Strand of the query EntryGroup. + **/ + private Strand getQueryReverseStrand () { + return getQueryEntryGroup ().getBases ().getReverseStrand (); + } + + final private JFrame parent_frame; + final private FeatureDisplay subject_feature_display; + final private FeatureDisplay query_feature_display; + final private AlignmentViewer alignment_viewer; + + private int subject_first_base_position = 1; + private int query_first_base_position = 1; + + private DisplayAdjustmentListener subject_listener = null; + private DisplayAdjustmentListener query_listener = null; + + /** + * The forward Strand of the subject EntryGroup when the Comparator was + * created. + **/ + final private Strand orig_subject_forward_strand; + + /** + * The forward Strand of the query EntryGroup when the Comparator was + * created. + **/ + final private Strand orig_query_forward_strand; + +} diff --git a/uk/ac/sanger/artemis/components/CorbaEntrySource.java b/uk/ac/sanger/artemis/components/CorbaEntrySource.java new file mode 100644 index 000000000..2c1950df2 --- /dev/null +++ b/uk/ac/sanger/artemis/components/CorbaEntrySource.java @@ -0,0 +1,90 @@ +/* CorbaEntrySource.java + * + * created: Wed Jun 7 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/CorbaEntrySource.java,v 1.1 2004-06-09 09:46:11 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.io.*; +import java.net.*; +import java.awt.*; + +import javax.swing.*; + +/** + * This class contains the methods common to all EntrySource implementations + * that read from a CORBA server. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: CorbaEntrySource.java,v 1.1 2004-06-09 09:46:11 tjc Exp $ + **/ + +abstract public class CorbaEntrySource { + /** + * Create a new CorbaEntrySource using the given URL as the location of the + * server IOR. + * @param frame The component that created this EntrySource. (Used for + * requesters.) + * @param ior_url_string A String containing the URL of the IOR for the + * server. + **/ + public CorbaEntrySource (final JFrame frame, + final String ior_url_string) + throws MalformedURLException //, IOException + { + this.frame = frame; + this.ior_url = new URL (ior_url_string); + } + + /** + * Finds and returns a stringified object reference (IOR) for a CORBA + * server. The IOR is read from the URL that was passed to the + * constructor. + * @return The stringified IOR. + * @exception IOException Thrown if an error occurs while reading. + **/ + public String getIOR () + throws IOException { + // get URL of IOR + final BufferedReader in = + new BufferedReader (new InputStreamReader (ior_url.openStream())); + return in.readLine(); + } + + /** + * Return the JFrame that was passed to the constructor. + **/ + public JFrame getFrame () { + return frame; + } + + /** + * The URL containing the IOR of the CORBA server. + **/ + private URL ior_url = null; + + /** + * The JFrame that was passed to the constructor. + **/ + private JFrame frame = null; +} diff --git a/uk/ac/sanger/artemis/components/DbfetchEntrySource.java b/uk/ac/sanger/artemis/components/DbfetchEntrySource.java new file mode 100644 index 000000000..435af2e29 --- /dev/null +++ b/uk/ac/sanger/artemis/components/DbfetchEntrySource.java @@ -0,0 +1,253 @@ +/* DbfetchEntrySource.java + * + * created: Fri Nov 28 2003 + * + * This file is part of Artemis + * + * Copyright (C) 2003 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/DbfetchEntrySource.java,v 1.1 2004-06-09 09:46:12 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.util.Document; +import uk.ac.sanger.artemis.util.DocumentFactory; +import uk.ac.sanger.artemis.io.DocumentEntryFactory; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; + +import java.io.*; +import javax.swing.*; + +/** + * This is an EntrySource that reads Entry objects from the EMBL Dbfetch + * server. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: DbfetchEntrySource.java,v 1.1 2004-06-09 09:46:12 tjc Exp $ + **/ + +public class DbfetchEntrySource + implements EntrySource +{ + /** + * Create a new DbfetchEntrySource. + * @param frame The component that created this EntrySource. (Used for + * requesters.) + **/ + public DbfetchEntrySource (final JFrame frame) + { + + } + + /** + * Get an Entry object from the Ensembl Dbfetch server. + * @param bases The Bases object to pass to the Entry constructor. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final Bases bases, ProgressThread progress_thread, + final boolean show_progress) + throws OutOfRangeException, IOException + { + return getEntry(bases,show_progress,progress_thread); + } + + /** + * Get an Entry object from the Ensembl Dbfetch server. + * @param bases The Bases object to pass to the Entry constructor. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final Bases bases, final boolean show_progress) + throws OutOfRangeException, IOException + { + return getEntry(bases,show_progress, null); + } + + /** + * Get an Entry object from the Ensembl Dbfetch server. + * @param bases The Bases object to pass to the Entry constructor. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final Bases bases, final boolean show_progress, + ProgressThread progress_thread) + throws OutOfRangeException, IOException + { + final TextDialog text_dialog = + new TextDialog (getFrame (), "Enter an accession number:", 10, ""); + + final String text = text_dialog.getText (); + + if (text == null) { + // user cancel + return null; + } + + final String embl_id = text.trim (); + + if(embl_id.length () > 0) + { + final LogReadListener read_event_logger = new LogReadListener (embl_id); + + final EntryInformation entry_information = + new SimpleEntryInformation (Options.getArtemisEntryInformation ()); + +// final MessageDialog message_frame = +// new MessageDialog (getFrame (), +// "reading entry - please wait", false); + + if(progress_thread != null) + progress_thread.start(); + + final String url_string = + "http://www.ebi.ac.uk/cgi-bin/dbfetch?db=EMBL&id=" + embl_id; + + final Document url_document = + DocumentFactory.makeDocument (url_string); + + try + { + final uk.ac.sanger.artemis.io.Entry new_embl_entry = + DocumentEntryFactory.makeDocumentEntry (entry_information, + url_document, + read_event_logger); + + if (read_event_logger.seenMessage ()) { + final YesNoDialog yes_no_dialog = + new YesNoDialog (frame, + "there were warnings while reading - view now?"); + + if (yes_no_dialog.getResult ()) { + Splash.showLog (); + } + } + + final Bases real_bases; + + if (bases == null) { + if (new_embl_entry.getSequence () == null) { + final String message = + "the entry contains no sequence: " + embl_id; + new MessageDialog (getFrame (), message); + return null; + } + + real_bases = new Bases (new_embl_entry.getSequence ()); + } else { + real_bases = bases; + } + + return new Entry (real_bases, new_embl_entry); + } + catch (EntryInformationException e) + { + throw new Error ("internal error - unexpected exception: " + e); + } +// finally +// { +// message_frame.dispose (); +// } + } + + return null; + } + + /** + * Get an Entry object from the Ensembl Dbfetch server. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final boolean show_progress) + throws OutOfRangeException, NoSequenceException, IOException + { + return getEntry (null, show_progress); + } + + /** + * Get an Entry object from the Ensembl Dbfetch server. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @param progress_thread Progress thread to monitor the entry reading. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final boolean show_progress, ProgressThread progress_thread) + throws OutOfRangeException, NoSequenceException, IOException + { + return getEntry (null, show_progress, progress_thread); + } + + + /** + * Return the name of this source (for display to the user in menus). + **/ + public String getSourceName () { + return "EBI - Dbfetch"; + } + + /** + * Returns true if and only if this EntrySource always returns "full" + * entries. ie. entries that contain features and sequence. Entries that + * are read from EMBL always contain sequence so in this class this method + * returns false. + **/ + public boolean isFullEntrySource () { + return true; + } + + /** + * Return the JFrame that was passed to the constructor. + **/ + public JFrame getFrame () { + return frame; + } + + /** + * The JFrame that was passed to the constructor. + **/ + private JFrame frame = null; +} diff --git a/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java b/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java new file mode 100644 index 000000000..fa70a88ba --- /dev/null +++ b/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java @@ -0,0 +1,186 @@ +/* DisplayAdjustmentEvent.java + * + * created: Tue Dec 15 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java,v 1.1 2004-06-09 09:46:13 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.ChangeEvent; + +/** + * This event is sent when a FeatureDisplay is scrolled. + * + * @author Kim Rutherford + * @version $Id: DisplayAdjustmentEvent.java,v 1.1 2004-06-09 09:46:13 tjc Exp $ + **/ + +public class DisplayAdjustmentEvent extends ChangeEvent +{ + + /** The new start base, as passed to the constructor. */ + private int start_base; + + /** The new end base, as passed to the constructor. */ + private int end_base; + + /** + * The width in bases of the display, as passed to the constructor. + **/ + private int width_in_bases; + + /** + * The width of each base on the display, as passed to the constructor. + **/ + private float base_width; + + /** The scale factor, as passed to the constructor. */ + private int scale_factor; + + /** + * True if and only if the FeatureDisplay is drawing in + * reverse complement mode. + **/ + private boolean rev_comp_display; + + /** + * The type of event. One of: SCALE_ADJUST_EVENT, SCROLL_ADJUST_EVENT or + * ALL_CHANGE_ADJUST_EVENT. + **/ + private int type; + + /** + * The type of DisplayAdjustmentEvent where the scale (only) has changed + **/ + final static public int SCALE_ADJUST_EVENT = 0; + + /** + * The type of DisplayAdjustmentEvent where the display has scrolled but + * the scale has stayed the same. + **/ + final static public int SCROLL_ADJUST_EVENT = 1; + + /** + * The type of DisplayAdjustmentEvent where the display has been reverse + * complemented. + **/ + final static public int REV_COMP_EVENT = 2; + + /** + * The type of DisplayAdjustmentEvent where the display has scrolled and + * the the scale has changed. + **/ + final static public int ALL_CHANGE_ADJUST_EVENT = 3; + + /** + * Create a new DisplayAdjustmentEvent. + * @param source The Object that generated the event - probably a component. + * @param start_base The new start base on the display. + * @param end_base The new end base on the display. + * @param width_in_bases The width of the drawing area in bases. We need + * this because if the user scrolls to the end of the sequence, the last + * visible base (end_base) may not be at the right of the screen. This + * might be greater than end_base - start_base. + * @param base_width The width in pixels of a base on screen. + * @param scale_factor This is the scale factor use by the FeatureDisplay + * component. A factor of zero means the full translation will be + * visible. At higher scale factors only stop codons are visible, and + * a bigger number will mean more bases are visible. + * @param type the type of event: SCALE_ADJUST_EVENT, SCROLL_ADJUST_EVENT + * or ALL_CHANGE_ADJUST_EVENT. + **/ + public DisplayAdjustmentEvent(Object source, + int start_base, int end_base, + int width_in_bases, float base_width, + int scale_factor, + boolean rev_comp_display, int type) + { + super(source); + this.start_base = start_base; + this.end_base = end_base; + this.width_in_bases = width_in_bases; + this.base_width = base_width; + this.scale_factor = scale_factor; + this.rev_comp_display = rev_comp_display; + this.type = type; + } + + /** + * Return the new start base to display, as passed to the constructor. + **/ + public int getStart() + { + return start_base; + } + + /** + * Return the new end base to display, as passed to the constructor. + **/ + public int getEnd() + { + return end_base; + } + + /** + * Return the width in bases of the display, as passed to the constructor. + **/ + public int getWidthInBases() + { + return width_in_bases; + } + + /** + * Return the width of a base on the display, as passed to the constructor. + **/ + public float getBaseWidth() + { + return base_width; + } + + /** + * Return the scale factor that was passed to the constructor. + **/ + public int getScaleFactor() + { + return scale_factor; + } + + /** + * Return true if and only if the FeatureDisplay is drawing in reverse + * complement mode. + **/ + public boolean isRevCompDisplay() + { + return rev_comp_display; + } + + /** + * Return the type that was passed to the constructor. + **/ + public int getType() + { + return type; + } + +} + + diff --git a/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java b/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java new file mode 100644 index 000000000..31bcc8296 --- /dev/null +++ b/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java @@ -0,0 +1,45 @@ +/* DisplayAdjustmentListener.java + * + * created: Tue Dec 15 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java,v 1.1 2004-06-09 09:46:14 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +/** + * The DisplayAdjustmentListener interface is implemented by those + * components that need to track the scrolling of a component that can + * display bases. For example, BasePlot objects listen to FeatureDisplay + * objects. + * + * @author Kim Rutherford + * @version $Id: DisplayAdjustmentListener.java,v 1.1 2004-06-09 09:46:14 tjc Exp $ + **/ + +public interface DisplayAdjustmentListener extends uk.ac.sanger.artemis.ChangeListener { + /** + * Invoked when a component scrolls or changes the scale. + **/ + void displayAdjustmentValueChanged (DisplayAdjustmentEvent event); +} + + diff --git a/uk/ac/sanger/artemis/components/DisplayComponent.java b/uk/ac/sanger/artemis/components/DisplayComponent.java new file mode 100644 index 000000000..7953d56d1 --- /dev/null +++ b/uk/ac/sanger/artemis/components/DisplayComponent.java @@ -0,0 +1,52 @@ +/* DisplayComponent.java (formally SelectionDisplayer.java) + * + * created: Fri Nov 13 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/DisplayComponent.java,v 1.1 2004-06-09 09:46:15 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.EntryGroup; +import uk.ac.sanger.artemis.Selection; +import uk.ac.sanger.artemis.GotoEventSource; + +import javax.swing.*; + +/** + * Interface discribing those methods common to all the classes in + * uk.ac.sanger.artemis.components that can display EntryGroup objects. + * + * @author Kim Rutherford + * @version $Id: DisplayComponent.java,v 1.1 2004-06-09 09:46:15 tjc Exp $ + **/ + +public interface DisplayComponent { + /** + * Return an object that implements the GotoEventSource interface. + **/ + GotoEventSource getGotoEventSource (); + + /** + * Return the reference of the JFrame that owns this component. + **/ + JFrame getParentFrame (); +} diff --git a/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java b/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java new file mode 100644 index 000000000..084012483 --- /dev/null +++ b/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java @@ -0,0 +1,318 @@ +/* EMBLCorbaEntrySource.java + * + * created: Wed Jun 7 2000 + * + * This file is part of Artemis + * + * Copyright (C) 1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:46:17 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.LocationParseException; +import uk.ac.sanger.artemis.io.InvalidKeyException; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; + +import nsdb.EmblSeq; +import nsdb.EmblPackage.Superceded; +import type.NoResult; + +import java.io.*; +import java.awt.*; +import java.net.*; + +import javax.swing.*; + +/** + * This is an EntrySource that reads Entry objects from the EMBL CORBA + * server. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: EMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:46:17 tjc Exp $ + **/ + +public class EMBLCorbaEntrySource + extends CorbaEntrySource + implements EntrySource +{ + /** + * Create a new EMBLCorbaEntrySource from the given String. + * @param frame The component that created this EntrySource. (Used for + * requesters.) + * @param ior_url_string A String containing the URL of the IOR for the + * server. + **/ + public EMBLCorbaEntrySource (final JFrame frame, + final String ior_url_string) + throws MalformedURLException//, IOException + { + super (frame, ior_url_string); + } + + /** + * Get an Entry object from the Ensembl CORBA server. + * @param bases The Bases object to pass to the Entry constructor. + * @param progress_thread Progress thread to monitor entry reading. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final Bases bases, final ProgressThread progress_thread, + final boolean show_progress) + throws OutOfRangeException, IOException + { + return makeCorbaDialog (bases, false); + } + + /** + * Get an Entry object from the Ensembl CORBA server. + * @param bases The Bases object to pass to the Entry constructor. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final Bases bases, final boolean show_progress) + throws OutOfRangeException, IOException + { + return makeCorbaDialog (bases, false); + } + + /** + * Get an Entry object from the Ensembl CORBA server. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @param progress_thread Progress thread to monitor entry reading. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final boolean show_progress, + final ProgressThread progress_thread) + throws OutOfRangeException, IOException + { + return makeCorbaDialog (null, false); + } + + + /** + * Get an Entry object from the Ensembl CORBA server. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. (Not implemented) + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final boolean show_progress) + throws OutOfRangeException, NoSequenceException, IOException { + return makeCorbaDialog (null, false); + } + + /** + * Return the name of this source (for display to the user in menus). + **/ + public String getSourceName () { + return "EBI - CORBA"; + } + + /** + * Returns true if and only if this EntrySource always returns "full" + * entries. ie. entries that contain features and sequence. Entries that + * are read from EMBL always contain sequence so in this class this method + * returns false. + **/ + public boolean isFullEntrySource () { + return true; + } + + /** + * Create a TextRequester, wait for the user to type an accession number + * and then read that entry from the EMBL CORBA server. + * @param bases If this is null a new Bases object will be created for the + * Entry once it has been read from the server. If not null then it will + * be passed to the Entry constructor. + * @param read_only true if and only if a read-only Entry should be created + * (some are always read only). + * @return null if and only if the user cancels the read or if the read + * fails. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + **/ + protected Entry makeCorbaDialog (final Bases bases, + final boolean read_only) + throws OutOfRangeException, IOException { + final org.omg.CORBA.ORB orb = + org.omg.CORBA.ORB.init (new String [0], new java.util.Properties()); + + final TextDialog text_dialog = + new TextDialog (getFrame (), "Enter an accession number:", 10, ""); + + final String text = text_dialog.getText (); + + if (text == null) { + // user cancel + return null; + } else { + final String corba_id = text.trim (); + + if (corba_id.length () > 0) { + final MessageDialog message_frame = + new MessageDialog (getFrame (), + "reading entry - please wait", false); + + try { + return makeFromCorbaID (bases, corba_id, read_only); + } finally { + message_frame.dispose (); + } + } else { + return null; + } + } + } + + /** + * Given an accession number and the handle of an EMBL corba server, this + * method will ask the user (using a TextRequester) for the id of a entry + * in the server and will then attempt to get it. + * @param bases If this is null a new Bases object will be created for the + * Entry once it has been read from the server. If not null then it will + * be passed to the Entry constructor. + * @param corba_handle The handle of the nsdb.Embl object from which we + * will read the entry. + * @param corba_id The id of the entry in the database + * @param read_only true if and only if a read-only Entry should be created + * (some are always read only). + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + **/ + protected Entry makeFromCorbaID (final Bases bases, + final String corba_id, + final boolean read_only) + throws OutOfRangeException, IOException { + try { + final nsdb.Embl corba_handle = getServerHandle (); + + final uk.ac.sanger.artemis.io.Entry new_embl_entry; + + final nsdb.EmblWriter embl_writer = + nsdb.EmblWriterHelper.narrow (corba_handle); + + final EntryInformation entry_information = + new SimpleEntryInformation (Options.getArtemisEntryInformation ()); + + if (read_only || embl_writer == null) { + // first try to make a plain EmblSeq object + final EmblSeq embl_seq = corba_handle.getEmblSeq (corba_id); + + new_embl_entry = + new uk.ac.sanger.artemis.io.CorbaEntry (entry_information, + embl_seq); + } else { + // make a read-write object + final nsdb.EmblSeqWriter embl_write_seq = + embl_writer.getEmblSeqWriter (corba_id); + + new_embl_entry = + new uk.ac.sanger.artemis.io.RWCorbaEntry (entry_information, + embl_write_seq); + } + + final Bases real_bases; + + if (bases == null) { + if (new_embl_entry.getSequence () == null) { + final String message = + "the entry contains no sequence: " + corba_id; + new MessageDialog (getFrame (), message); + return null; + } + + real_bases = new Bases (new_embl_entry.getSequence ()); + } else { + real_bases = bases; + } + + return new Entry (real_bases, new_embl_entry); + } catch (NoResult e) { + final String message = + "Database query failed (no result) while getting id: " + corba_id; + new MessageDialog (getFrame (), message); + } catch (Superceded e) { + // Superceded is thrown by getEmblSeq method if accession number + // doesn't exist anymore because it was merged or split + final String message = + "This accession number has been superceded: " + corba_id; + new MessageDialog (getFrame (), message); + } catch (LocationParseException e) { + final String message = + "Unexpected error while accessing " + corba_id + ": " + e; + new MessageDialog (getFrame (), message); + } catch (InvalidKeyException e) { + final String message = + "Unexpected error while accessing " + corba_id + ": " + e; + new MessageDialog (getFrame (), message); + } catch (org.omg.CORBA.OBJECT_NOT_EXIST e) { + final String message = + "the object you requested (" + corba_id + ") does not exist"; + new MessageDialog (getFrame (), message); + } catch (org.omg.CORBA.COMM_FAILURE e) { + final String message = + "Failed to get an object from Corba: " + e; + new MessageDialog (getFrame (), message); + } catch (EntryInformationException e) { + final String message = + "Failed to get an object from Corba: " + e; + new MessageDialog (getFrame (), message); + } + + return null; + } + + /** + * Return the handle of the EMBL database. The database IOR is found by + * calling getIOR (). + **/ + protected nsdb.Embl getServerHandle () + throws org.omg.CORBA.COMM_FAILURE, IOException { + final org.omg.CORBA.ORB orb = + org.omg.CORBA.ORB.init (new String [0], new java.util.Properties()); + + final org.omg.CORBA.Object obj; + + obj = orb.string_to_object (getIOR ()); + + return nsdb.EmblHelper.narrow (obj); + } +} diff --git a/uk/ac/sanger/artemis/components/EditMenu.java b/uk/ac/sanger/artemis/components/EditMenu.java new file mode 100644 index 000000000..aafe11393 --- /dev/null +++ b/uk/ac/sanger/artemis/components/EditMenu.java @@ -0,0 +1,2447 @@ +/* EditMenu.java + * + * created: Thu Dec 3 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000,2001,2002 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/EditMenu.java,v 1.1 2004-06-09 09:46:18 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.Range; +import uk.ac.sanger.artemis.io.RangeVector; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.Location; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.QualifierInfo; +import uk.ac.sanger.artemis.io.QualifierInfoVector; +import uk.ac.sanger.artemis.io.QualifierVector; +import uk.ac.sanger.artemis.io.QualifierParseException; +import uk.ac.sanger.artemis.io.InvalidQualifierException; +import uk.ac.sanger.artemis.io.InvalidRelationException; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.io.OutOfDateException; + +import java.awt.*; +import java.awt.event.*; +import java.io.IOException; + +import javax.swing.*; + +/** + * A menu with editing commands. + * + * @author Kim Rutherford + * @version $Id: EditMenu.java,v 1.1 2004-06-09 09:46:18 tjc Exp $ + **/ + +public class EditMenu extends SelectionMenu + implements EntryGroupChangeListener, EntryChangeListener { + /** + * Create a new EditMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param entry_group The EntryGroup object where new features/entries will + * be added. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + * @param menu_name The name of the new menu. + **/ + public EditMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group, + final String menu_name) { + super (frame, menu_name, selection); + + this.entry_group = entry_group; + this.goto_event_source = goto_event_source; + this.base_plot_group = base_plot_group; + + getEntryGroup ().addEntryGroupChangeListener (this); + getEntryGroup ().addEntryChangeListener (this); + + refreshMenu (); + } + + /** + * Create a new EditMenu object and use "Edit" as the menu name. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param entry_group The EntryGroup object where new features/entries will + * be added. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public EditMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group) { + this (frame, selection, goto_event_source, entry_group, + base_plot_group, "Edit"); + } + + /** + * The shortcut for Edit Selected Features. + **/ + final static KeyStroke EDIT_FEATURES_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_E, InputEvent.CTRL_MASK); + final static public int EDIT_FEATURES_KEY_CODE = KeyEvent.VK_E; + + /** + * The shortcut for Merge Selected Features. + **/ + final static KeyStroke MERGE_FEATURES_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_M, InputEvent.CTRL_MASK); + final static public int MERGE_FEATURES_KEY_CODE = KeyEvent.VK_M; + + /** + * The shortcut for Duplicate Selected Features. + **/ + final static KeyStroke DUPLICATE_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_D, InputEvent.CTRL_MASK); + final static public int DUPLICATE_KEY_CODE = KeyEvent.VK_D; + + /** + * The shortcut for Delete Selected Features. + **/ + final static KeyStroke DELETE_FEATURES_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_DELETE, InputEvent.CTRL_MASK); + final static public int DELETE_FEATURES_KEY_CODE = KeyEvent.VK_DELETE; + + /** + * The shortcut for Trim Selected Features. + **/ + final static KeyStroke TRIM_FEATURES_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_T, InputEvent.CTRL_MASK); + final static public int TRIM_FEATURES_KEY_CODE = KeyEvent.VK_T; + + /** + * The shortcut for Trim Selected Features To Next Any. + **/ + final static KeyStroke TRIM_FEATURES_TO_NEXT_ANY_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_Y, InputEvent.CTRL_MASK); + final static public int TRIM_FEATURES_TO_NEXT_ANY_KEY_CODE = KeyEvent.VK_Y; + + /** + * The shortcut for Extend to Previous Stop Codon. + **/ + final static public int EXTEND_TO_PREVIOUS_STOP_CODON_KEY_CODE = + KeyEvent.VK_Q; + final static KeyStroke EXTEND_TO_PREVIOUS_STOP_CODON_KEY = + makeMenuKeyStroke (EXTEND_TO_PREVIOUS_STOP_CODON_KEY_CODE); + + /** + * The shortcut for Undo. + **/ + final static public int UNDO_KEY_CODE = KeyEvent.VK_U; + final static KeyStroke UNDO_KEY = + KeyStroke.getKeyStroke (UNDO_KEY_CODE, InputEvent.CTRL_MASK); + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can update the display if entries + * are added or deleted. + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.ENTRY_ADDED: + case EntryGroupChangeEvent.ENTRY_DELETED: + case EntryGroupChangeEvent.ENTRY_INACTIVE: + case EntryGroupChangeEvent.ENTRY_ACTIVE: + case EntryGroupChangeEvent.NEW_DEFAULT_ENTRY: + refreshMenu (); + break; + } + } + + /** + * Implementation of the EntryChangeListener interface. + **/ + public void entryChanged (final EntryChangeEvent event) { + if (event.getType () == EntryChangeEvent.NAME_CHANGED) { + refreshMenu (); + } + } + + /** + * Update the menus to the reflect the current contents of the EntryGroup. + **/ + private void refreshMenu () { + removeAll (); + + undo_item = new JMenuItem ("Undo"); + undo_item.setAccelerator (UNDO_KEY); + undo_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + undo (getParentFrame (), getSelection (), getEntryGroup ()); + } + }); + + edit_feature_item = new JMenuItem ("Edit Selected Features"); + edit_feature_item.setAccelerator (EDIT_FEATURES_KEY); + edit_feature_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + editSelectedFeatures (getParentFrame (), getEntryGroup (), + getSelection (), getGotoEventSource ()); + } + }); + + edit_subsequence_item = new JMenuItem ("Edit Subsequence (and Features)"); + edit_subsequence_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + editSubSequence (); + } + }); + + add_qualifiers_item = new JMenuItem ("Change Qualifiers Of Selected ..."); + add_qualifiers_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + addQualifiers (getParentFrame (), getSelection ()); + } + }); + + remove_qualifier_item = new JMenuItem ("Remove Qualifier Of Selected ..."); + remove_qualifier_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + removeQualifier (getParentFrame (), getSelection ()); + } + }); + + + merge_features_item = new JMenuItem ("Merge Selected Features"); + merge_features_item.setAccelerator (MERGE_FEATURES_KEY); + merge_features_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + mergeFeatures (getParentFrame (), getSelection (), getEntryGroup ()); + } + }); + + unmerge_feature_item = new JMenuItem ("Unmerge Selected Feature"); + unmerge_feature_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + unmergeFeature (getParentFrame (), getSelection (), getEntryGroup ()); + } + }); + + duplicate_item = new JMenuItem ("Duplicate Selected Features"); + duplicate_item.setAccelerator (DUPLICATE_KEY); + duplicate_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + duplicateFeatures (getParentFrame (), getSelection (), + getEntryGroup ()); + } + }); + + delete_features_item = new JMenuItem ("Delete Selected Features"); + delete_features_item.setAccelerator (DELETE_FEATURES_KEY); + delete_features_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + deleteSelectedFeatures (getParentFrame (), getSelection (), + getEntryGroup ()); + } + }); + + delete_segments_item = new JMenuItem ("Delete Selected Exons"); + delete_segments_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + deleteSelectedSegments (); + } + }); + + delete_introns_item = + new JMenuItem ("Remove Introns of Selected Features"); + delete_introns_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + removeIntrons (); + } + }); + + edit_header_item = new JMenuItem ("Edit Header Of Default Entry"); + edit_header_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + editHeader (); + } + }); + + move_features_menu = new JMenu ("Move Selected Features To"); + copy_features_menu = new JMenu ("Copy Selected Features To"); + + if (entry_group == null || getEntryGroup ().size () == 0) { + move_features_menu.add (new JMenuItem ("(No Entries Currently)")); + copy_features_menu.add (new JMenuItem ("(No Entries Currently)")); + } else { + for (int i = 0 ; i < getEntryGroup ().size () ; ++i) { + final Entry this_entry = getEntryGroup ().elementAt (i); + + String entry_name = this_entry.getName (); + + if (entry_name == null) { + entry_name = "no name"; + } + + final JMenuItem move_to_item = new JMenuItem (entry_name); + + move_to_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + // unselect, move, then reselect (for speed) + final FeatureVector selected_features = + getSelection ().getAllFeatures (); + getSelection ().clear (); + moveFeatures (selected_features, this_entry); + getSelection ().set (selected_features); + } + }); + move_features_menu.add (move_to_item); + + final JMenuItem copy_to_item = new JMenuItem (entry_name); + + copy_to_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + copyFeatures (getSelection ().getAllFeatures (), this_entry); + } + }); + copy_features_menu.add (copy_to_item); + } + } + + trim_to_any_item = new JMenuItem ("Trim Selected Features To Any"); + trim_to_any_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + EditMenu.trimSelected (getParentFrame (), getSelection (), + getEntryGroup (), true, false); + } + }); + + trim_item = new JMenuItem ("Trim Selected Features To Met"); + trim_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + EditMenu.trimSelected (getParentFrame (), getSelection (), + getEntryGroup (), false, false); + } + }); + + trim_to_next_any_item = + new JMenuItem ("Trim Selected Features To Next Any"); + trim_to_next_any_item.setAccelerator (TRIM_FEATURES_TO_NEXT_ANY_KEY); + trim_to_next_any_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + EditMenu.trimSelected (getParentFrame (), getSelection (), + getEntryGroup (), true, true); + } + }); + + trim_to_next_item = new JMenuItem ("Trim Selected Features To Next Met"); + trim_to_next_item.setAccelerator (TRIM_FEATURES_KEY); + trim_to_next_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + EditMenu.trimSelected (getParentFrame (), getSelection (), + getEntryGroup (), false, true); + } + }); + + extend_to_prev_stop_item = + new JMenuItem ("Extend to Previous Stop Codon"); + extend_to_prev_stop_item.setAccelerator (EXTEND_TO_PREVIOUS_STOP_CODON_KEY); + extend_to_prev_stop_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + extendToORF (getParentFrame (), getSelection (), + getEntryGroup (), false); + } + }); + + extend_to_next_stop_item = new JMenuItem ("Extend to Next Stop Codon"); + extend_to_next_stop_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + extendToORF (getParentFrame (), getSelection (), + getEntryGroup (), true); + } + }); + + fix_stop_codons_item = new JMenuItem ("Fix Stop Codons"); + fix_stop_codons_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + fixStopCodons (); + } + }); + + auto_gene_name_item = new JMenuItem ("Automatically Create Gene Names"); + auto_gene_name_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + autoGeneName (); + } + }); + + fix_gene_names_item = new JMenuItem ("Fix Gene Names"); + fix_gene_names_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + fixGeneNames (getParentFrame (), getEntryGroup (), + getSelection ()); + } + }); + + reverse_complement_item = new JMenuItem ("Reverse And Complement"); + reverse_complement_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + reverseAndComplement (); + } + }); + + delete_bases_item = new JMenuItem ("Delete Selected Bases"); + delete_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + deleteSelectedBases (); + } + }); + + add_bases_item = new JMenuItem ("Add Bases At Selection"); + add_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + addBases (); + } + }); + + if (Options.readWritePossible ()) { + // only the standalone version can save or read + add_bases_from_file_item = new JMenuItem ("Add Bases From File ..."); + add_bases_from_file_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + addBasesFromFile (); + } + }); + } + + if (Options.getOptions ().getPropertyTruthValue ("val_mode")) { + add (edit_feature_item); + add (edit_subsequence_item); + addSeparator (); + add (edit_header_item); + addSeparator (); + } + + if (Options.getOptions ().getUndoLevels () > 0) { + add (undo_item); + addSeparator (); + } + + if (!Options.getOptions ().getPropertyTruthValue ("val_mode")) { + add (edit_feature_item); + add (edit_subsequence_item); + addSeparator (); + add (edit_header_item); + addSeparator (); + } + + add (add_qualifiers_item); + add (remove_qualifier_item); + add (duplicate_item); + add (merge_features_item); + add (unmerge_feature_item); + add (delete_features_item); + add (delete_segments_item); + add (delete_introns_item); + addSeparator (); + add (move_features_menu); + add (copy_features_menu); + addSeparator (); + add (trim_item); + add (trim_to_any_item); + add (trim_to_next_item); + add (trim_to_next_any_item); + add (extend_to_prev_stop_item); + add (extend_to_next_stop_item); + add (fix_stop_codons_item); + addSeparator (); + add (auto_gene_name_item); + add (fix_gene_names_item); + add (reverse_complement_item); + add (delete_bases_item); + add (add_bases_item); + if (Options.readWritePossible ()) { + // only the standalone version can save or read + add (add_bases_from_file_item); + } + } + + /** + * Undo the last change by calling ActionController.undo(). + * @param frame The Frame to use for MessageDialog components. + * @param selection The current Selection - needs to be cleared before undo + * @param entry_group Used to get the ActionController for calling + * ActionController.undo(). + **/ + public static void undo (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + if (Options.getOptions ().getUndoLevels () == 0) { + // undo disabled + return; + } + + if (entry_group.getActionController ().canUndo ()) { + // clear the selection because something in the selection might + // disappear after the undo() eg. create a feature, select it then undo + selection.clear (); + } + + if (!entry_group.getActionController ().undo ()) { + new MessageDialog (frame, "sorry - no further undo information"); + } + } + + /** + * editSelectedFeatures () etc. will only edit this many features. + **/ + private static final int MAXIMUM_SELECTED_FEATURES = 25; + + /** + * Open an edit window (FeatureEdit) for each of the selected features. + * The edit component will listen for feature change events and update + * itself. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The selected features to edit. + * @param entry_group Used to get the ActionController for calling + * startAction() and endAction(). + **/ + static void editSelectedFeatures (final JFrame frame, + final EntryGroup entry_group, + final Selection selection, + final GotoEventSource goto_event_source) { + final FeatureVector features_to_edit = selection.getAllFeatures (); + + if (features_to_edit.size () > MAXIMUM_SELECTED_FEATURES) { + new MessageDialog (frame, "warning: only editing the first " + + MAXIMUM_SELECTED_FEATURES + + " selected features"); + } + + for (int i = 0 ; + i < features_to_edit.size () && i < MAXIMUM_SELECTED_FEATURES ; + ++i) { + final Feature selection_feature = features_to_edit.elementAt (i); + + new FeatureEdit (selection_feature, entry_group, selection, + goto_event_source).show (); + } + + selection.set (features_to_edit); + } + + /** + * Create a new EntryEdit component that contains only the selected + * sequence and the features in the selected range. + **/ + private void editSubSequence () { + if (getSelection ().isEmpty ()) { + new MessageDialog (getParentFrame (), "nothing selected"); + } + + final Range range = getSelection ().getSelectionRange (); + + final EntryGroup new_entry_group = getEntryGroup ().truncate (range); + + new EntryEdit (new_entry_group).show (); + } + + /** + * Open a EntryHeaderEdit window for the default entry. + **/ + private void editHeader () { + final Entry default_entry = getEntryGroup ().getDefaultEntry (); + + if (default_entry == null) { + final String message = + "there is no default entry"; + new MessageDialog (getParentFrame (), message); + } else { + if (default_entry.isReadOnly ()) { + new MessageDialog (getParentFrame (), + "the default entry is read-only " + + "- cannot continue"); + return; + } + + new EntryHeaderEdit (entry_group, default_entry); + } + } + + /** + * Merge the selected features into one Feature. If there are selected + * segments then the owning Feature of each segment will be the Feature + * that is merged. This method will create a new Feature. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the features to merge. + * @param entry_group Used to get the ActionController for calling + * startAction() and endAction(). + **/ + static void mergeFeatures (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionFeatures (frame, selection, + 10, "really merge all (>10) " + + "selected features?")) { + return; + } + + final FeatureVector features_to_merge = selection.getAllFeatures (); + + if (features_to_merge.size () < 2) { + new MessageDialog (frame, + "nothing to merge - select more than one feature"); + return; + } + + final Feature merge_feature = features_to_merge.elementAt (0); + + // make sure all the features are on the same strand + for (int i = 1 ; i < features_to_merge.size () ; ++i) { + final Feature this_feature = features_to_merge.elementAt (i); + + if (this_feature.isForwardFeature () != + merge_feature.isForwardFeature ()) { + new MessageDialog (frame, + "all the features in a merge must be on the " + + "same strand"); + return; + } + + if (! this_feature.getKey ().equals (merge_feature.getKey ())) { + new MessageDialog (frame, + "all the features in a merge must have the " + + "same key"); + return; + } + } + + if (Options.getOptions ().isNoddyMode ()) { + final YesNoDialog dialog = + new YesNoDialog (frame, + "Are you sure you want to merge the selected " + + "features?"); + + if (!dialog.getResult ()) { + return; + } + } + + final Feature new_feature; + + try { + new_feature = merge_feature.duplicate (); + } catch (ReadOnlyException e) { + final String message = + "one or more of the features is read-only or is in a " + + "read-only entry - cannot continue"; + new MessageDialog (frame, message); + return; + } + + for (int i = 1 ; i < features_to_merge.size () ; ++i) { + final Feature this_feature = features_to_merge.elementAt (i); + + final QualifierVector qualifiers = this_feature.getQualifiers (); + + for (int qualifier_index = 0 ; + qualifier_index < qualifiers.size () ; + ++qualifier_index) { + final Qualifier this_qualifier = + qualifiers.elementAt (qualifier_index); + + try { + new_feature.addQualifierValues (this_qualifier); + } catch (EntryInformationException e) { + try { + new_feature.removeFromEntry (); + } catch (ReadOnlyException _) { + // give up ... + } + final String message = + "destination entry does not support all the qualifiers " + + "needed by " + this_feature.getIDString (); + new MessageDialog (frame, message); + } catch (ReadOnlyException e) { + final String message = "the new feature is read-only so " + + "some qualifiers have been lost"; + new MessageDialog (frame, message); + } + } + + final FeatureSegmentVector segments = this_feature.getSegments (); + + for (int segment_index = 0 ; + segment_index < segments.size () ; + ++segment_index) { + final FeatureSegment this_segment = + segments.elementAt (segment_index); + + final Range this_range = this_segment.getRawRange (); + + try { + new_feature.addSegment (this_range); + } catch (ReadOnlyException e) { + final String message = + "merging failed because the entry is read-only"; + new MessageDialog (frame, message); + try { + new_feature.removeFromEntry (); + } catch (ReadOnlyException _) { + // oh dear ... + } + } + } + } + + // this is set to true when a merge is done in the next (inner) loop + boolean keep_looping = true; + + // this is a bit inefficient, but there aren't normally many segments + LOOP: + while (keep_looping) { + final FeatureSegmentVector feature_segments = + new_feature.getSegments (); + + keep_looping = false; + + // now merge overlapping ranges + for (int segment_index = 0 ; + segment_index < feature_segments.size () - 1; + ++segment_index) { + + final FeatureSegment this_segment = + feature_segments.elementAt (segment_index); + final MarkerRange this_range = this_segment.getMarkerRange (); + + final FeatureSegment next_segment = + feature_segments.elementAt (segment_index + 1); + final MarkerRange next_range = next_segment.getMarkerRange (); + + // if it overlaps the next Range then merge it + if (this_range.overlaps (next_range) && + this_segment.getFrameID () == next_segment.getFrameID ()) { + try { + final Range new_range = + this_range.combineRanges (next_range, false).getRawRange (); + new_feature.addSegment (new_range); + new_feature.removeSegment (this_segment); + new_feature.removeSegment (next_segment); + + // start again + keep_looping = true; + continue LOOP; + } catch (ReadOnlyException e) { + final String message = + "merging failed because the entry is read-only"; + new MessageDialog (frame, message); + } catch (LastSegmentException e) { + throw new Error ("internal error - tried to remove " + + "last segment: " + e); + } + } + } + } + + boolean delete_old_features; + + if (Options.getOptions ().isNoddyMode ()) { + final YesNoDialog delete_old_dialog = + new YesNoDialog (frame, "delete old features?"); + + delete_old_features = delete_old_dialog.getResult (); + } else { + delete_old_features = true; + } + + if (delete_old_features) { + if (getReadOnlyFeatures (features_to_merge).size () > 0) { + new MessageDialog (frame, "deletion failed because the features " + + "are read-only"); + } else { + for (int i = 0 ; i < features_to_merge.size () ; ++i) { + try { + features_to_merge.elementAt (i).removeFromEntry (); + } catch (ReadOnlyException e) { + new MessageDialog (frame, "deletion failed one or more of the " + + "features are read-only"); + } + } + } + } + + selection.set (new_feature); + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * If the selection contains exactly two segments and those segments are + * adjacent in the same feature, split the feature into two pieces. The + * orignal feature is truncated and a new feature is created. The + * qualifiers of the old feature are copied to new feature. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the segments to unmerge. + * @param entry_group Used to get the ActionController for calling + * startAction() and endAction(). + **/ + static void unmergeFeature (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + try { + entry_group.getActionController ().startAction (); + + final FeatureSegmentVector selected_segments = + selection.getSelectedSegments (); + + if (selected_segments.size () != 2) { + final String message = + "you need to select exactly two exons use unmerge"; + new MessageDialog (frame, message); + return; + } + + FeatureSegment first_segment = selected_segments.elementAt (0); + FeatureSegment second_segment = selected_segments.elementAt (1); + + if (first_segment.getFeature () != second_segment.getFeature ()) { + final String message = + "you need to select two exons from the same feature to use unmerge"; + new MessageDialog (frame, message); + return; + } + + final Feature segment_feature = first_segment.getFeature (); + + final FeatureSegmentVector all_feature_segments = + segment_feature.getSegments (); + + int index_of_first_segment = + all_feature_segments.indexOf (first_segment); + int index_of_second_segment = + all_feature_segments.indexOf (second_segment); + + if (index_of_first_segment - index_of_second_segment < -1 || + index_of_first_segment - index_of_second_segment > 1) { + final String message = + "you need to select two adjacent exons to use unmerge"; + new MessageDialog (frame, message); + return; + } + + if (index_of_second_segment < index_of_first_segment) { + // swap the segments for consistency + final FeatureSegment temp_segment = first_segment; + final int temp_segment_index = index_of_first_segment; + + first_segment = second_segment; + index_of_first_segment = index_of_second_segment; + + second_segment = temp_segment; + index_of_second_segment = temp_segment_index; + } + + try { + final Feature new_feature = segment_feature.duplicate (); + + // we set the Selection later + selection.clear (); + + // delete the segments starting at index_of_second_segment from + // segment_feature and delete the segments up to (and including) + // index_of_first_segment from new_feature + + for (int i = all_feature_segments.size () - 1 ; + i >= index_of_second_segment ; + --i) { + segment_feature.getSegments ().elementAt (i).removeFromFeature (); + } + + // remove the first segment of new_feature index_of_first_segment times + for (int i = 0 ; i <= index_of_first_segment ; ++i) { + new_feature.getSegments ().elementAt (0).removeFromFeature (); + } + + selection.set (segment_feature.getSegments ().lastElement ()); + selection.add (new_feature.getSegments ().elementAt (0)); + } catch (ReadOnlyException e) { + final String message = + "the selected exons (in " + + segment_feature.getIDString () + + ") are in a read only entry - cannot continue"; + new MessageDialog (frame, message); + } catch (LastSegmentException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Create a QualifierEditor JFrame that acts on the selected features. + * @param frame The JFrame to use for MessageDialog components. + **/ + private void addQualifiers (final JFrame frame, final Selection selection) { + if (!checkForSelectionFeatures (frame, selection)) { + return; + } + + final FeatureVector selected_features = selection.getAllFeatures (); + + if (getReadOnlyFeatures (selected_features).size () > 0) { + new MessageDialog (frame, + "one or more of the selected features is read-only " + + "- cannot continue"); + return; + } + + final QualifierEditor qualifier_editor = + new QualifierEditor (selected_features, getEntryGroup ()); + + qualifier_editor.setVisible (true); + } + + /** + * Offer the user a choice of qualifier to remove from the selected features + **/ + private void removeQualifier (final JFrame frame, final Selection selection) { + if (!checkForSelectionFeatures (frame, selection)) { + return; + } + + final FeatureVector selected_features = selection.getAllFeatures (); + + final StringVector qualifier_names = + Feature.getAllQualifierNames (selected_features); + + if (qualifier_names.size () == 0) { + new MessageDialog (getParentFrame (), "feature has no qualifiers"); + return; + } + + final ChoiceFrame choice_frame = + new ChoiceFrame ("Select a qualifer name", qualifier_names); + + final JComboBox choice = choice_frame.getChoice (); + + + final int MAX_VISIBLE_ROWS = 30; + + choice.setMaximumRowCount (MAX_VISIBLE_ROWS); + + choice.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + removeQualifierFromFeatures (selected_features, + (String) choice.getSelectedItem ()); + choice_frame.setVisible (false); + choice_frame.dispose (); + } + }); + + choice_frame.getOKButton ().addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + removeQualifierFromFeatures (selected_features, + (String) choice.getSelectedItem ()); + } + }); + + choice_frame.setVisible (true); + } + + /** + * Remove the qualifier given by qualifier_name from the given features. + * Silently ignore features that don't have that qualifier. Warn the user + * about read-only features. + **/ + private void removeQualifierFromFeatures (final FeatureVector features, + final String qualifier_name) { + boolean found_read_only = false; + + try { + entry_group.getActionController ().startAction (); + + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + try { + this_feature.removeQualifierByName (qualifier_name); + } catch (OutOfDateException _) { + // ignore + } catch (EntryInformationException _) { + // ignore + } catch (ReadOnlyException _) { + found_read_only = true; + } + } + + if (found_read_only) { + final String message = + "ignored one or more read-only features"; + new MessageDialog (getParentFrame (), message); + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Duplicate the selected Feature objects. If there are selected segments + * then the owning Feature of each segment will be duplicated. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the features to merge. + * @param entry_group Used to get the ActionController for calling + * startAction() and endAction(). + **/ + static void duplicateFeatures (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + try { + entry_group.getActionController ().startAction (); + + if (getReadOnlyFeatures (selection.getAllFeatures ()).size () > 0) { + new MessageDialog (frame, + "one or more of the selected features is read-only " + + "- cannot continue"); + return; + } + + if (Options.getOptions ().isNoddyMode ()) { + final YesNoDialog dialog = + new YesNoDialog (frame, + "Are you sure you want to duplicate the selected " + + "features?"); + + if (!dialog.getResult ()) { + return; + } + } else { + if (!checkForSelectionFeatures (frame, selection, + 100, "really duplicate all (>100) " + + "selected features?")) { + return; + } + } + + final FeatureVector features_to_duplicate = + selection.getAllFeatures (); + + for (int i = 0 ; i < features_to_duplicate.size () ; ++i) { + final Feature this_feature = features_to_duplicate.elementAt (i); + + try { + this_feature.duplicate (); + } catch (ReadOnlyException e) { + final String message = + "one of the selected features (" + this_feature.getIDString () + + ") is read only - cannot continue"; + new MessageDialog (frame, message); + return; + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Delete the selected features. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the features to merge. + * @param entry_group Used to get the ActionController for calling + * startAction() and endAction(). + **/ + static void deleteSelectedFeatures (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + try { + entry_group.getActionController ().startAction (); + + final FeatureVector features_to_delete = selection.getAllFeatures (); + + final String feature_count_string; + + if (features_to_delete.size () == 1) { + feature_count_string = "the selected feature"; + } else { + feature_count_string = features_to_delete.size () + " features"; + } + + if (Options.getOptions ().isNoddyMode ()) { + // 0 means always popup a YesNoDialog + if (!checkForSelectionFeatures (frame, selection, 0, + "really delete " + + feature_count_string + "?")) { + return; + } + } + + // clear the selection now so that it doesn't need to be updated as each + // feature is deleted + selection.clear (); + + while (features_to_delete.size () > 0) { + // delete in reverse order for speed + + final Feature current_selection_feature = + features_to_delete.lastElement (); + + features_to_delete.removeElementAt (features_to_delete.size () - 1); + + try { + current_selection_feature.removeFromEntry (); + } catch (ReadOnlyException e) { + selection.set (current_selection_feature); + + if (features_to_delete.size () == 1) { + final String message = + "the selected feature (" + + current_selection_feature.getIDString () + + ") is read only - cannot continue"; + new MessageDialog (frame, message); + + } else { + final String message = + "one of the selected features (" + + current_selection_feature.getIDString () + + ") is read only - cannot continue"; + new MessageDialog (frame, message); + } + + features_to_delete.add (current_selection_feature); + + // reset the select so that the user can see what it was + selection.set (features_to_delete); + + return; + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Delete the selected feature segments. + **/ + private void deleteSelectedSegments () { + try { + entry_group.getActionController ().startAction (); + + final FeatureSegmentVector segments_to_delete = + (FeatureSegmentVector) getSelection ().getSelectedSegments ().clone (); + + if (Options.getOptions ().isNoddyMode ()) { + // 0 means always popup a YesNoDialog + if (!checkForSelectionFeatureSegments (0, "really delete " + + (segments_to_delete.size ()==1 ? + "the selected exon?" : + segments_to_delete.size () + + " exons?"))) { + return; + } + } + + for (int i = 0 ; i < segments_to_delete.size () ; ++i) { + final FeatureSegment selection_segment = + segments_to_delete.elementAt (i); + + try { + getSelection ().remove (selection_segment); + selection_segment.removeFromFeature (); + } catch (ReadOnlyException e) { + final String message = + "one of the selected exons (in " + + selection_segment.getFeature ().getIDString () + + ") is in a read only - cannot continue"; + new MessageDialog (getParentFrame (), message); + } catch (LastSegmentException e) { + final String message = + "the last exon in this feature: " + + selection_segment.getFeature ().getIDString () + + " cannot be removed (all features must have at least one exon)"; + new MessageDialog (getParentFrame (), message); + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Remove all introns from the selected features. + **/ + private void removeIntrons () { + if (!checkForSelectionFeatures ()) { + return; + } + + boolean found_read_only = false; + + try { + entry_group.getActionController ().startAction (); + + final FeatureVector features = getSelection ().getAllFeatures (); + + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + try { + final RangeVector new_ranges = new RangeVector (); + final Range new_range = this_feature.getMaxRawRange (); + new_ranges.add (new_range); + final Location old_location = this_feature.getLocation (); + final Location new_location = + new Location (new_ranges, old_location.isComplement ()); + this_feature.setLocation (new_location); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (ReadOnlyException _) { + found_read_only = true; + } + } + + if (found_read_only) { + final String message = + "ignored one or more read-only features"; + new MessageDialog (getParentFrame (), message); + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Move the given features to the given destination entry. + **/ + private void moveFeatures (final FeatureVector features, + final Entry destination_entry) { + try { + entry_group.getActionController ().startAction (); + + if (features.size () == 0) { + return; + } + + final FeatureVector read_only_features = + getReadOnlyFeatures (features); + + if (read_only_features.size () > 0) { + final String message = + (features.size () == 1 ? + "the selected feature (" + + read_only_features.elementAt (0).getIDString () + + ") is read only " : + "some of the selected features (eg. " + + read_only_features.elementAt (0).getIDString () + + ") are read only - ") + + "cannot continue"; + new MessageDialog (getParentFrame (), message); + return; + } + + FEATURES: + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + + // check that we don't loop forever (perhaps due to bugs in + // handleOpenException()) + final int MAX_COUNT = 100; + + int count = 0; + + while (count++ < MAX_COUNT) { + try { + this_feature.moveTo (destination_entry, false); + continue FEATURES; + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (EntryInformationException e) { + final EntryInformation dest_entry_information = + destination_entry.getEntryInformation (); + dest_entry_information.fixException (e); + continue; + } catch (ReadOnlyException e) { + final String message = + "either the source or destination for one of the features is " + + "read only - cannot continue"; + new MessageDialog (getParentFrame (), message); + return; + } + } + + final String message = + "internal error while copying"; + new MessageDialog (getParentFrame (), message); + + throw new Error ("internal error in EditMenu.copyFeatures() - infinite loop"); + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Copy the given features to the given destination entry. + **/ + private void copyFeatures (final FeatureVector features, + final Entry destination_entry) { + try { + entry_group.getActionController ().startAction (); + + FEATURES: + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + + // check that we don't loop forever (perhaps due to bugs in + // handleOpenException()) + final int MAX_COUNT = 100; + + int count = 0; + + while (count++ < MAX_COUNT) { + try { + this_feature.copyTo (destination_entry); + continue FEATURES; + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (EntryInformationException e) { + final EntryInformation dest_entry_information = + destination_entry.getEntryInformation (); + dest_entry_information.fixException (e); + continue; + } catch (ReadOnlyException e) { + final String message = + "the destination entry is read only - cannot continue"; + new MessageDialog (getParentFrame (), message); + return; + } + } + + final String message = "internal error while copying"; + new MessageDialog (getParentFrame (), message); + + throw new Error ("internal error in EditMenu.copyFeatures() - " + + "infinite loop"); + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Calls fixStopCodon () on each selected feature. + **/ + private void fixStopCodons () { + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionFeatures ()) { + return; + } + + // features that can't be fixed + final FeatureVector bad_features = new FeatureVector (); + + final FeatureVector features_to_fix = getSelection ().getAllFeatures (); + + for (int i = 0 ; i < features_to_fix.size () ; ++i) { + final Feature selection_feature = features_to_fix.elementAt (i); + + if (!selection_feature.isCDS ()) { + final String message = + "Warning: some of the selected features are not coding features. " + + "Continue?"; + + final YesNoDialog yes_no_dialog = + new YesNoDialog (getParentFrame (), message); + + if (yes_no_dialog.getResult ()) { + break; + } else { + return; + } + } + } + + for (int i = 0 ; i < features_to_fix.size () ; ++i) { + final Feature selection_feature = features_to_fix.elementAt (i); + + try { + if (!selection_feature.fixStopCodon ()) { + bad_features.add (selection_feature); + } + } catch (ReadOnlyException e) { + final String message = + "one of the entries is read only - cannot continue"; + new MessageDialog (getParentFrame (), message); + break; + } + } + + if (bad_features.size () > 0) { + // select the bad feature + getSelection ().set (bad_features); + + final String message = + "Warning: could not fix the stop codon for some of the features. " + + "View them now?"; + + final YesNoDialog yes_no_dialog = + new YesNoDialog (getParentFrame (), message); + + if (yes_no_dialog.getResult ()) { + final FeaturePredicate predicate = + new FeatureFromVectorPredicate (bad_features); + + final String filter_name = + "features that can't be fixed (filtered from: " + + getParentFrame ().getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + getSelection (), + goto_event_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Calls Feature.trimStart () on all selected Feature objects. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group Used to get the ActionController for calling + * startAction(). + * @param trim_to_any If true then the features will be trimmed to ATG, GTG + * or TTG, otherwise the features will be trimmed to ATG only. + * @param trim_to_next If true then the features will be trimmed to the + * next start codon (dependent on trim_to_any) regardless of whether the + * feature currently start on a start codon. If false then the feature + * will only be trimmed if the feature doesn't start on a start codon. + **/ + static public void trimSelected (final JFrame frame, + final Selection selection, + final EntryGroup entry_group, + final boolean trim_to_any, + final boolean trim_to_next) { + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionFeatures (frame, selection)) { + return; + } + + final FeatureVector features_to_trim = selection.getAllFeatures (); + + // this will contain the features that could not be trimmed + final FeatureVector failed_features = new FeatureVector (); + + for (int i = 0 ; i < features_to_trim.size () ; ++i) { + final Feature selection_feature = features_to_trim.elementAt (i); + + try { + if (!selection_feature.trimStart (trim_to_any, trim_to_next)) { + failed_features.add (selection_feature); + } + } catch (ReadOnlyException e) { + final String message = + "one or more of the of the selected features are read only " + + "- cannot continue"; + new MessageDialog (frame, message); + break; + } + } + + if (failed_features.size () > 0) { + selection.set (failed_features); + + if (failed_features.size () == 1) { + final String message = "could not trim the feature"; + + new MessageDialog (frame, message); + + } else { + final String message = + "some features could not be trimmed (they are now selected)"; + + new MessageDialog (frame, message); + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Extend the selected segments/features so that they reach to the start or + * end of their containing ORF. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group Used to get the ActionController for calling + * startAction() and endAction(). + * @param extend_to_next_stop If true the feature is extended to the next + * stop codon (but it won't include the next stop). If false the feature + * is extended to the previous stop codon (but it won't include the + * previous stop). + **/ + public static void extendToORF (final JFrame frame, + final Selection selection, + final EntryGroup entry_group, + final boolean extend_to_next_stop) { + try { + entry_group.getActionController ().startAction (); + + if (!checkForSelectionFeatures (frame, selection)) { + return; + } + + final FeatureVector features_to_extend = selection.getAllFeatures (); + + for (int i = 0 ; i < features_to_extend.size () ; ++i) { + final Feature selection_feature = features_to_extend.elementAt (i); + + if (selection_feature.isReadOnly ()) { + final String message = + "one or more of the of the selected features are read only " + + "- cannot continue"; + new MessageDialog (frame, message); + break; + } + + if (selection_feature.getSegments ().size () < 1) { + continue; + } + + final FeatureSegmentVector feature_segments = + selection_feature.getSegments (); + + if (feature_segments.size () == 0) { + continue; + } + + final Strand strand = selection_feature.getStrand (); + + if (extend_to_next_stop) { + if (selection_feature.hasValidStopCodon ()) { + continue; + } + + final Marker feature_end_marker = + selection_feature.getLastBaseMarker (); + + Marker end_marker_copy = null; + + // get the marker of the first base of the last codon in the range + // (we want to keep in the same frame) + try { + if (selection_feature.getBaseCount () == 1) { + end_marker_copy = feature_end_marker; + } else { + if (selection_feature.getBaseCount () == 2) { + end_marker_copy = feature_end_marker.moveBy (-1); + } else { + // go to the start of the codon + end_marker_copy = feature_end_marker.moveBy (-2); + } + } + + // adjust for /codon_start + final int frame_shift = + selection_feature.getCodonStart () - 1; + + // the number of bases from the start of translation to the + // end of the feature + final int bases_from_start_pos_mod_3 = + (3 + selection_feature.getBaseCount () - frame_shift) % 3; + + end_marker_copy = + end_marker_copy.moveBy (-bases_from_start_pos_mod_3); + } catch (OutOfRangeException e) { + end_marker_copy = feature_end_marker; + } + + final MarkerRange end_orf_range = + strand.getORFAroundMarker (end_marker_copy, true); + + if (end_orf_range == null) { + // end_marker_copy is at the start base of a stop codon + continue; + } + + final Marker new_end_marker = end_orf_range.getEnd (); + + try { + feature_end_marker.setPosition (new_end_marker.getPosition ()); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + } else { + final AminoAcidSequence translation = + selection_feature.getTranslation (); + + if (translation.length () > 0) { + final char first_aa = translation.elementAt (0); + + if (AminoAcidSequence.isStopCodon (first_aa)) { + continue; + } + } + + final Marker feature_start_marker = + selection_feature.getFirstBaseMarker (); + + Marker start_marker_copy = feature_start_marker; + + final int frame_shift = + selection_feature.getCodonStart () - 1; + + if (frame_shift != 0) { + try { + start_marker_copy = + feature_start_marker.moveBy (frame_shift); + } catch (OutOfRangeException e) { + // ignore and hope for the best + } + } + + final MarkerRange start_orf_range = + strand.getORFAroundMarker (start_marker_copy, true); + + final Marker new_start_marker = start_orf_range.getStart (); + + try { + feature_start_marker.setPosition (new_start_marker.getPosition ()); + selection_feature.removeQualifierByName ("codon_start"); + } catch (EntryInformationException _) { + // ignore - if we can't remove codon_start, then it won't have + // been there to start with + } catch (OutOfDateException _) { + // ignore + } catch (ReadOnlyException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Reverse and complement the sequence and all the features in all Entry + * objects. + **/ + private void reverseAndComplement () { + if (getEntryGroup ().isReadOnly ()) { + final String message = + "one or more of the entries or features are read only - " + + "cannot continue"; + new MessageDialog (getParentFrame (), message); + return; + } + + final YesNoDialog dialog = + new YesNoDialog (getParentFrame (), + "Are you sure you want to reverse complement all " + + "entries?"); + + if (!dialog.getResult ()) { + return; + } + + try { + getEntryGroup ().reverseComplement (); + + makeSelectionStartVisible (); + } catch (ReadOnlyException e) { + final String message = + "one of the entries is read only - cannot continue"; + new MessageDialog (getParentFrame (), message); + return; + } + } + + /** + * Delete the selected bases after asking the user for confimation. + **/ + private void deleteSelectedBases () { + if (!checkForSelectionRange ()) { + return; + } + + final MarkerRange marker_range = getSelection ().getMarkerRange (); + + if (marker_range.getCount () == getEntryGroup ().getSequenceLength ()) { + new MessageDialog (getParentFrame (), "You can't delete every base"); + return; + } + + if (getEntryGroup ().isReadOnly ()) { + new MessageDialog (getParentFrame (), + "one of the current entries or features is " + + "read-only - connot continue"); + return; + } + + final Range raw_range = marker_range.getRawRange (); + + final FeatureVector features_in_range; + + final EntryVector old_active_entries = + getEntryGroup ().getActiveEntries (); + + // make all the entries active so that getFeaturesInRange () gets + // everything + for (int i = 0 ; i < getEntryGroup ().size () ; ++i) { + getEntryGroup ().setIsActive (i, true); + } + + try { + features_in_range = + getEntryGroup ().getFeaturesInRange (raw_range); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + if (features_in_range.size () != 0) { + final FeatureVector read_only_features = + getReadOnlyFeatures (features_in_range); + + if (read_only_features.size () > 0) { + getSelection ().set (read_only_features); + final String message = + "one or more of the features in the selected range are " + + "read only - cannot continue"; + new MessageDialog (getParentFrame (), message); + getSelection ().setMarkerRange (marker_range); + return; + } + + for (int feature_index = 0 ; + feature_index < features_in_range.size () ; + ++feature_index) { + final Feature this_feature = + features_in_range.elementAt (feature_index); + + final FeatureSegmentVector segments = this_feature.getSegments (); + + for (int i = 0 ; i < segments.size () ; ++i) { + final FeatureSegment this_segment = segments.elementAt (i); + + final Range this_segment_range = this_segment.getRawRange (); + + if (raw_range.getStart () <= this_segment_range.getStart () && + raw_range.getEnd () >= this_segment_range.getStart () || + raw_range.getStart () <= this_segment_range.getEnd () && + raw_range.getEnd () >= this_segment_range.getEnd ()) { + new MessageDialog (getParentFrame (), + "the selected range overlaps the " + + "start or end of an exon - cannot continue"); + return; + } + } + } + } + + // reset the EntryGroup to the state it was in originally + for (int i = 0 ; i < getEntryGroup ().size () ; ++i) { + final Entry this_entry = getEntryGroup ().elementAt (i); + final boolean old_active_flag = + old_active_entries.contains (this_entry); + getEntryGroup ().setIsActive (i, old_active_flag); + } + + if (Options.getOptions ().isNoddyMode ()) { + final YesNoDialog dialog = + new YesNoDialog (getParentFrame (), + "Are you sure you want to delete the " + + "selected bases?"); + if (!dialog.getResult ()) { + return; + } + } + + try { + getSelection ().setMarkerRange (null); + + // deleteRange () is static and uses the strand reference from the + // MarkerRange to decide which Strand to edit + Strand.deleteRange (marker_range); + } catch (ReadOnlyException e) { + getSelection ().setMarkerRange (marker_range); + + new MessageDialog (getParentFrame (), + "the bases are read only - cannot delete"); + return; + } + } + + /** + * Ask the user for some bases and then add them at the start of the + * selected range. + **/ + private void addBases () { + if (!checkForSelectionRange ()) { + return; + } + + if (getEntryGroup ().isReadOnly ()) { + new MessageDialog (getParentFrame (), + "one of the current entries or features is " + + "read-only - connot continue"); + return; + } + + final MarkerRange range = getSelection ().getMarkerRange (); + + final TextRequester text_requester = + new TextRequester ("enter the bases to insert before base " + + range.getStart ().getPosition () + + (range.isForwardMarker () ? + " on the forward strand" : + " on the reverse strand") + ":", + 18, ""); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + if (event.getType () == TextRequesterEvent.CANCEL) { + return; + } + + final String bases_string = event.getRequesterText ().trim (); + + if (bases_string.length () == 0) { + new MessageDialog (getParentFrame (), "no bases inserted"); + return; + } + + try { + // addBases () is static and uses the strand reference from the + // start Marker to decide which Strand to edit + Strand.addBases (range.getStart (), bases_string); + } catch (ReadOnlyException e) { + new MessageDialog (getParentFrame (), + "sorry the bases are read only"); + return; + } catch (org.biojava.bio.symbol.IllegalSymbolException e) { + new MessageDialog (getParentFrame (), + "sorry - new sequence contains non-IUB base " + + "codes"); + return; + } + } + }); + + text_requester.show (); + } + + + /** + * Ask the user for some bases and then add them at the start of the + * selected range. + **/ + private void addBasesFromFile () { + if (!checkForSelectionRange ()) { + return; + } + + if (getEntryGroup ().isReadOnly ()) { + new MessageDialog (getParentFrame (), + "one of the current entries or features is " + + "read-only - connot continue"); + return; + } + + final MarkerRange range = getSelection ().getMarkerRange (); + + // XXX add an InputStreamProgressListener + final EntrySourceVector entry_sources = + Utilities.getEntrySources (getParentFrame (), null); + + EntrySource filesystem_entry_source = null; + + for (int source_index = 0 ; + source_index < entry_sources.size () ; + ++source_index) { + final EntrySource this_source = + entry_sources.elementAt (source_index); + + if (this_source.isFullEntrySource ()) { + continue; + } + + if (this_source.getSourceName ().equals ("Filesystem")) { + filesystem_entry_source = this_source; + } + } + + if (filesystem_entry_source == null) { + throw new Error ("internal error - can't find a file system to read " + + "from"); + } + + try { + final Entry new_entry = filesystem_entry_source.getEntry (true); + + final Bases bases = new_entry.getBases (); + + final String bases_string = bases.toString (); + + if (bases_string.length () == 0) { + new MessageDialog (getParentFrame (), "no bases inserted"); + return; + } + + // addBases () is static and uses the strand reference from the + // start Marker to decide which Strand to edit + Strand.addBases (range.getStart (), bases_string); + } catch (ReadOnlyException e) { + new MessageDialog (getParentFrame (), + "sorry the bases are read only"); + return; + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while reading: " + e.getMessage ()); + return; + } catch (OutOfRangeException e) { + new MessageDialog (getParentFrame (), + "out of range exception while reading: " + + e.getMessage ()); + return; + } catch (NoSequenceException e) { + new MessageDialog (getParentFrame (), + "sorry the file did not contain any sequence"); + return; + } catch (org.biojava.bio.symbol.IllegalSymbolException e) { + new MessageDialog (getParentFrame (), + "sorry - new sequence contains non-IUB base " + + "codes"); + return; + } + } + + /** + * Add a qualifier to the given features. The qualifier will normally be a + * gene name. + * @param prefix_string prefix of the new qualifier eg. SPAC + * @param start_number The number to start counting at eg. 1 give SPAC0001 + * for the first CDS + * @param increment The amount to increment by at each gene. + * @param qualifier_name The name of the qualifier to add + * @param tag_complement_names If True a lowercase "c" will be appended to + * the new qualifier values on the reverse strand. + **/ + private void autoGeneNameHelper (final FeatureVector features_to_name, + final String prefix_string, + final int start_number, + final int increment, + final String qualifier_name, + final boolean tag_complement_names) { + try { + entry_group.getActionController ().startAction (); + + int current_number = start_number; + + for (int i = 0 ; i < features_to_name.size () ; ++i) { + final Feature this_feature = features_to_name.elementAt (i); + + final Key key = this_feature.getKey (); + + if (key.equals ("CDS")) { + final String number_string; + + if (current_number < 10) { + number_string = "000" + current_number; + } else { + if (current_number < 100) { + number_string = "00" + current_number; + } else { + if (current_number < 1000) { + number_string = "0" + current_number; + } else { + number_string = String.valueOf (current_number); + } + } + } + + try { + final Qualifier new_qualifier = + new Qualifier (qualifier_name, prefix_string + + number_string + + (!tag_complement_names || + this_feature.isForwardFeature () ? + "" : + "c")); + + this_feature.addQualifierValues (new_qualifier); + } catch (EntryInformationException e) { + new MessageDialog (getParentFrame (), + "/" + qualifier_name + + " not supported by this entry " + + "- cannot continue"); + return; + } catch (ReadOnlyException e) { + new MessageDialog (getParentFrame (), + "one of the features is in a read-only " + + "entry - cannot continue"); + return; + } + + current_number += increment; + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Automatically create gene names for all CDS features in the active + * entries. + **/ + private void autoGeneName () { + if (!checkForSelectionFeatures (getParentFrame (), getSelection ())) { + return; + } + + final FeatureVector features_to_name = getSelection ().getAllFeatures (); + + if (getReadOnlyFeatures (features_to_name).size () > 0) { + new MessageDialog (getParentFrame (), + "one or more of the current features is " + + "read-only - cannot continue"); + return; + } + + final TextDialog prefix_dialog = + new TextDialog (getParentFrame (), + "enter the start characters of the new gene names:", + 18, ""); + + final String prefix_text = prefix_dialog.getText (); + + if (prefix_text == null) { + return; + } + + final String prefix_string = prefix_text.trim (); + + if (prefix_string.length () == 0) { + new MessageDialog (getParentFrame (), "no prefix given"); + return; + } + + final TextDialog start_number_dialog = + new TextDialog (getParentFrame (), + "start counting at:", 18, "1"); + + final String start_number_text = start_number_dialog.getText (); + + if (start_number_text == null) { + return; + } + + String start_string = start_number_text.trim (); + + if (start_string.length () == 0) { + new MessageDialog (getParentFrame (), "no start given"); + return; + } + + final int start_value; + + try { + start_value = Integer.valueOf (start_string).intValue (); + } catch (NumberFormatException e) { + new MessageDialog (getParentFrame (), + "this is not a number: " + start_string); + return; + } + + final TextDialog increment_dialog = + new TextDialog (getParentFrame (), + "increment number by:", 18, "1"); + + final String increment_text = increment_dialog.getText (); + + if (increment_text == null) { + return; + } + + String increment_string = increment_text.trim (); + + if (increment_string.length () == 0) { + new MessageDialog (getParentFrame (), "no increment given"); + return; + } + + final int increment_value; + + try { + increment_value = Integer.valueOf (increment_string).intValue (); + } catch (NumberFormatException e) { + new MessageDialog (getParentFrame (), + "this is not a number: " + increment_string); + return; + } + + final TextDialog qualifier_name_dialog = + new TextDialog (getParentFrame (), + "enter a qualifier name to use", + 18, "gene"); + + final String qualifier_name_text = qualifier_name_dialog.getText (); + + if (qualifier_name_text == null) { + return; + } + + final String qualifier_name_string = qualifier_name_text.trim (); + + if (qualifier_name_string.length () == 0) { + new MessageDialog (getParentFrame (), "no qualifier name given"); + return; + } + + final YesNoDialog complement_tag_dialog = + new YesNoDialog (getParentFrame (), + "append \"c\" to names of reverse strand features?"); + + autoGeneNameHelper (features_to_name, + prefix_string, start_value, increment_value, + qualifier_name_string, + complement_tag_dialog.getResult ()); + } + + /** + * Helper function for fixGeneNames(). Add the gene name from the CDS to + * neighbouring/overlapping mRNA, intron, exon, gene, 5'UTR and 3'UTR + * features. Warn about inconsistencies. + * @return true if all went well, false if fixing should stop immediately + **/ + private static boolean fixGeneNamesHelper (final JFrame frame, + final EntryGroup entry_group, + final Feature cds_to_fix) { + if (cds_to_fix.isReadOnly ()) { + final String message = + "one or more of the of the selected features are read only " + + "- cannot continue"; + new MessageDialog (frame, message); + return false; + } + + try { + final Strand cds_to_fix_strand = cds_to_fix.getStrand (); + + Marker cds_start_marker = cds_to_fix.getFirstBaseMarker (); + Marker cds_end_marker = cds_to_fix.getLastBaseMarker (); + + // move the start one base back and the end one base forward so that + // when we call getFeaturesInRange() we get the UTRs + + try { + cds_start_marker = cds_start_marker.moveBy (-1); + } catch (OutOfRangeException _) { + // ignore and use the original cds_start_marker + } + + try { + cds_end_marker = cds_end_marker.moveBy (1); + } catch (OutOfRangeException _) { + // ignore and use the original cds_end_marker + } + + final Range search_range; + + if (cds_start_marker.getRawPosition () < + cds_end_marker.getRawPosition ()) { + search_range = + new Range (cds_start_marker.getRawPosition (), + cds_end_marker.getRawPosition ()); + } else { + search_range = + new Range (cds_end_marker.getRawPosition (), + cds_start_marker.getRawPosition ()); + } + + final FeatureVector features_in_range = + entry_group.getFeaturesInRange (search_range); + + final FeatureVector features_to_change = + new FeatureVector (); + + for (int i = 0 ; i < features_in_range.size () ; ++i) { + final Feature this_feature = features_in_range.elementAt (i); + + if (this_feature.getStrand () == cds_to_fix_strand && + (this_feature.isCDS () || + this_feature.getKey ().equals ("mRNA") || + this_feature.getKey ().equals ("intron") || + this_feature.getKey ().equals ("exon") || + this_feature.getKey ().equals ("5'UTR") && + (cds_to_fix.getFirstBase () == + this_feature.getLastBase () + 1) || + this_feature.getKey ().equals ("3'UTR") && + (cds_to_fix.getLastBase () + 1 == + this_feature.getFirstBase ()) || + this_feature.getKey ().equals ("gene"))) { + + if (this_feature.isReadOnly ()) { + final String message = + "one or more of the of the overlapping features are read only " + + "- cannot continue"; + new MessageDialog (frame, message); + return false; + } + features_to_change.add (this_feature); + } + } + + final FeatureVector overlapping_cds_features = new FeatureVector (); + + for (int i = 0 ; i < features_to_change.size () ; ++i) { + final Feature this_test_feature = features_to_change.elementAt (i); + + if (this_test_feature != cds_to_fix && + this_test_feature.isCDS ()) { + overlapping_cds_features.add (this_test_feature); + features_to_change.remove (this_test_feature); + } + } + + if (overlapping_cds_features.size () > 0) { + final String message = + "your CDS (" + cds_to_fix.getIDString () + + ") overlaps " + overlapping_cds_features.size () + + " other CDS feature" + + (overlapping_cds_features.size () == 1 ? + "" : "s") + " - continue?"; + + final YesNoDialog dialog = + new YesNoDialog (frame, message); + + if (!dialog.getResult ()) { + return false; + } + } + + final StringVector gene_names = new StringVector (); + + for (int feature_index = 0 ; + feature_index < features_to_change.size () ; + ++feature_index) { + final Feature test_feature = + features_to_change.elementAt (feature_index); + + final StringVector test_feature_gene_names = + test_feature.getValuesOfQualifier ("gene"); + + if (test_feature_gene_names != null) { + for (int test_feature_gene_name_index = 0 ; + test_feature_gene_name_index < test_feature_gene_names.size (); + ++test_feature_gene_name_index) { + final String this_gene_name = + test_feature_gene_names.elementAt (test_feature_gene_name_index); + + if (!gene_names.contains (this_gene_name)) { + gene_names.add (this_gene_name); + } + } + } + } + + if (gene_names.size () == 0) { + // ignore this feature, but continue with the other features + return true; + } + + for (int feature_index = 0 ; + feature_index < features_to_change.size () ; + ++feature_index) { + final Feature this_feature = + features_to_change.elementAt (feature_index); + final Qualifier qualifier = new Qualifier ("gene", gene_names); + + this_feature.setQualifier (qualifier); + } + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (InvalidRelationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (EntryInformationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } catch (ReadOnlyException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + return true; + } + + /** + * For each selected CDS, add the gene name from the CDS to + * neighbouring/overlapping mRNA, intron, exon, gene, 5'UTR and 3'UTR + * features. Warn about inconsistencies. + * @param frame The Frame to use for MessageDialog components. + * @param selection The Selection that the commands in the menu will + * operate on. + **/ + public static void fixGeneNames (final JFrame frame, + final EntryGroup entry_group, + final Selection selection) { + try { + entry_group.getActionController ().startAction (); + + final FeatureVector features_to_fix = selection.getAllFeatures (); + + int cds_features_found = 0; + + for (int i = 0 ; i < features_to_fix.size () ; ++i) { + final Feature selection_feature = features_to_fix.elementAt (i); + + if (selection_feature.isCDS ()) { + ++cds_features_found; + if (!fixGeneNamesHelper (frame, entry_group, selection_feature)) { + return; + } + } + } + + if (cds_features_found == 0) { + new MessageDialog (frame, "no CDS features selected"); + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Return the EntryGroup that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + /** + * This method sends an GotoEvent to all the GotoEvent listeners that will + * make the first base of the selection visible. + **/ + private void makeSelectionStartVisible () { + final GotoEvent new_event = + new GotoEvent (this, getSelection ().getStartBaseOfSelection ()); + + goto_event_source.sendGotoEvent (new_event); + } + + /** + * Return the GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource getGotoEventSource () { + return goto_event_source; + } + + /** + * Returns a Vector containing those features in the given vector of + * features which are read only or are in a read only entry. + **/ + private static FeatureVector + getReadOnlyFeatures (final FeatureVector features) { + final FeatureVector return_vector = new FeatureVector (); + + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + if (this_feature.isReadOnly ()) { + return_vector.add (this_feature); + } + } + + return return_vector; + } + + /** + * The GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource goto_event_source = null; + + /** + * The EntryGroup object that was passed to the constructor. + **/ + private EntryGroup entry_group = null; + + /** + * The BasePlotGroup object that was passed to the constructor. + **/ + private BasePlotGroup base_plot_group = null; + + private JMenuItem undo_item = null; + private JMenuItem edit_feature_item = null; + private JMenuItem raw_edit_feature_item = null; + private JMenuItem duplicate_item = null; + private JMenuItem merge_features_item = null; + private JMenuItem unmerge_feature_item = null; + private JMenuItem delete_features_item = null; + private JMenuItem edit_header_item = null; + private JMenuItem add_qualifiers_item = null; + private JMenuItem remove_qualifier_item = null; + private JMenuItem delete_segments_item = null; + private JMenuItem delete_introns_item = null; + private JMenuItem edit_subsequence_item = null; + private JMenuItem paste_item = null; + private JMenu move_features_menu = null; + private JMenu copy_features_menu = null; + private JMenuItem trim_item = null; + private JMenuItem trim_to_any_item = null; + private JMenuItem trim_to_next_item = null; + private JMenuItem trim_to_next_any_item = null; + private JMenuItem extend_to_next_stop_item = null; + private JMenuItem extend_to_prev_stop_item = null; + private JMenuItem auto_label_item = null; + private JMenuItem auto_gene_name_item = null; + private JMenuItem fix_gene_names_item = null; + private JMenuItem fix_stop_codons_item = null; + private JMenuItem reverse_complement_item = null; + private JMenuItem delete_bases_item = null; + private JMenuItem add_bases_item = null; + private JMenuItem add_bases_from_file_item = null; +} diff --git a/uk/ac/sanger/artemis/components/EntryActionListener.java b/uk/ac/sanger/artemis/components/EntryActionListener.java new file mode 100644 index 000000000..057a82d11 --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryActionListener.java @@ -0,0 +1,63 @@ +/* EntryActionListener.java + * + * created: Mon Jan 31 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryActionListener.java,v 1.1 2004-06-09 09:46:21 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.awt.event.*; + +/** + * This class is an implementation of the ActionListener interface that can + * remember an entry. + * + * @author Kim Rutherford + * @version $Id: EntryActionListener.java,v 1.1 2004-06-09 09:46:21 tjc Exp $ + **/ + +abstract public class EntryActionListener implements ActionListener { + /** + * Make a new EntryActionListener from the given Entry. + **/ + EntryActionListener (final EntryEdit entry_edit, + final Entry entry) { + this.entry = entry; + this.entry_edit = entry_edit; + } + + abstract public void actionPerformed (final ActionEvent event); + + public Entry getEntry () { + return entry; + } + + public EntryEdit getEntryEdit () { + return entry_edit; + } + + final private Entry entry; + final private EntryEdit entry_edit; +} + diff --git a/uk/ac/sanger/artemis/components/EntryEdit.java b/uk/ac/sanger/artemis/components/EntryEdit.java new file mode 100644 index 000000000..2bec3a1eb --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryEdit.java @@ -0,0 +1,1434 @@ +/* EntryEdit.java + * + * created: Fri Oct 9 1998 + * + * This file is part of Artemis + * + * Copyright(C) 1998,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/EntryEdit.java,v 1.1 2004-06-09 09:46:24 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.Marker; +import uk.ac.sanger.artemis.sequence.Bases; + +import uk.ac.sanger.artemis.util.FileDocument; +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.util.ReadOnlyException; +import uk.ac.sanger.artemis.io.DocumentEntryFactory; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.awt.dnd.*; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.BevelBorder; + +/** + * Each object of this class is used to edit an EntryGroup object. + * + * @author Kim Rutherford + * @version $Id: EntryEdit.java,v 1.1 2004-06-09 09:46:24 tjc Exp $ + * + */ + +public class EntryEdit extends JFrame + implements EntryGroupChangeListener, EntryChangeListener, + DropTargetListener +{ + + /** The shortcut for Delete Selected Features. */ + final static KeyStroke SAVE_DEFAULT_KEY = + KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK); + + /** + * A vector containing the Entry objects that this + * EntryEdit object knows about. + **/ + private EntryGroup entry_group; + + /** + * Created by the constructor to pass to those objects + * that are interested in GotoEvents. + **/ + private GotoEventSource goto_event_source; + + private final JMenuBar menu_bar = new JMenuBar(); + private final JMenu file_menu = new JMenu("File"); + + private EntryGroupDisplay group_display; + private FeatureDisplay one_line_per_entry_display; + private FeatureDisplay feature_display; + private FeatureDisplay base_display; + private BasePlotGroup base_plot_group; + private FeatureList feature_list; + + /** This Object contains the current selection. */ + private Selection selection = null; + + /** + * The EntrySourceVector reference that is created in the constructor. + **/ + private EntrySourceVector entry_sources; + + + /** + * Create a new EntryEdit object and JFrame. + * @param entry_group The EntryGroup object that this component is editing. + */ + public EntryEdit(final EntryGroup entry_group) + { + super("Artemis Entry Edit"); + + setDropTarget(new DropTarget(this,this)); + entry_group.ref(); + this.entry_group = entry_group; + + // XXX add a InputStreamProgressListener + this.entry_sources = Utilities.getEntrySources(this, null); + this.goto_event_source = new SimpleGotoEventSource(getEntryGroup()); + + selection = new Selection(null); + + getEntryGroup().addFeatureChangeListener(selection); + getEntryGroup().addEntryChangeListener(selection); + getEntryGroup().addEntryGroupChangeListener(this); + getEntryGroup().addEntryChangeListener(this); + + if(getEntryGroup().getDefaultEntry() != null) + { + final String name = getEntryGroup().getDefaultEntry().getName(); + if(name != null) + setTitle("Artemis Entry Edit: " + name); + } + + final int font_height; + final int font_base_line; + + final Font default_font = getDefaultFont(); + + if(default_font != null) + { + FontMetrics fm = getFontMetrics(default_font); + font_height = fm.getHeight(); + font_base_line = fm.getMaxAscent(); + } + else + { + font_height = -1; + font_base_line = -1; + } + + addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent event) + { + closeEntryEdit(); + } + }); + + Box box_panel = Box.createVerticalBox(); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(box_panel, "North"); + + menu_bar.setFont(default_font); + + SelectionInfoDisplay selection_info = + new SelectionInfoDisplay(getEntryGroup(), getSelection()); + box_panel.add(selection_info); + + group_display = new EntryGroupDisplay(this); + box_panel.add(group_display); + + final boolean entry_buttons_option = + Options.getOptions().getPropertyTruthValue("show_entry_buttons"); + + group_display.setVisible(entry_buttons_option); + + base_plot_group = + new BasePlotGroup(getEntryGroup(), this, getSelection(), + getGotoEventSource()); + box_panel.add(base_plot_group); + + base_plot_group.setVisible(true); + + one_line_per_entry_display = + new FeatureDisplay(getEntryGroup(), getSelection(), + getGotoEventSource(), base_plot_group); + + one_line_per_entry_display.setShowLabels(false); + one_line_per_entry_display.setOneLinePerEntry(true); + + box_panel.add(one_line_per_entry_display); + one_line_per_entry_display.setVisible(false); + + feature_display = + new FeatureDisplay(getEntryGroup(), getSelection(), + getGotoEventSource(), base_plot_group); + + final Options options = Options.getOptions(); + + if(options.getProperty("overview_feature_labels") != null) + { + final boolean option_value = + options.getPropertyTruthValue("overview_feature_labels"); + feature_display.setShowLabels(option_value); + } + + if(options.getProperty("overview_one_line_per_entry") != null) + { + final boolean option_value = + options.getPropertyTruthValue("overview_one_line_per_entry"); + feature_display.setOneLinePerEntry(option_value); + } + + feature_display.addDisplayAdjustmentListener(base_plot_group); + feature_display.addDisplayAdjustmentListener(one_line_per_entry_display); + + one_line_per_entry_display.addDisplayAdjustmentListener(feature_display); + + box_panel.add(feature_display); + + feature_display.setVisible(true); + + base_display = + new FeatureDisplay(getEntryGroup(), getSelection(), + getGotoEventSource(), base_plot_group); + base_display.setShowLabels(false); + base_display.setScaleFactor(0); + box_panel.add(base_display); + + final boolean show_base_view; + + if(Options.getOptions().getProperty("show_base_view") != null) + show_base_view = + Options.getOptions().getPropertyTruthValue("show_base_view"); + else + show_base_view = true; + + base_display.setVisible(show_base_view); + + feature_list = + new FeatureList(getEntryGroup(), getSelection(), + getGotoEventSource(), base_plot_group); + feature_list.setFont(default_font); + + final boolean list_option_value = + Options.getOptions().getPropertyTruthValue("show_list"); + + if(list_option_value) + feature_list.setVisible(true); + else + feature_list.setVisible(false); + + getContentPane().add(feature_list, "Center"); + makeMenus(); + pack(); + + ClassLoader cl = this.getClass().getClassLoader(); + ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif")); + + if(icon != null) + { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + final Image icon_image = icon.getImage(); + MediaTracker tracker = new MediaTracker(this); + tracker.addImage(icon_image, 0); + + try + { + tracker.waitForAll(); + setIconImage(icon_image); + } + catch(InterruptedException e) + { + // ignore and continue + } + } + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + int screen_height = screen.height; + int screen_width = screen.width; + + if(screen_width <= 900 || screen_height <= 700) + setSize(screen_width * 9 / 10, screen_height * 9 / 10); + else + setSize(900, 700); + + Utilities.centreFrame(this); + } + + /** + * If there are no unsaved changes, close this EntryEdit. Otherwise ask + * the user first. + **/ + private void closeEntryEdit() + { + if(getEntryGroup().hasUnsavedChanges() && + getEntryGroup().refCount() == 1) + { + final YesNoDialog yes_no_dialog = + new YesNoDialog(EntryEdit.this, + "there are unsaved changes - really close?"); + + if(!yes_no_dialog.getResult()) + return; + } + + entryEditFinished(); + } + + /** + * Redraw this component. This method is public so that other classes can + * force an update if, for example, the options files is re-read. + **/ + public void redisplay() + { + feature_display.redisplay(); + base_display.redisplay(); + } + + /** + * This method sends an GotoEvent to all the GotoEvent listeners that will + * make the start of the first selected Feature or FeatureSegment visible. + **/ + public void makeSelectionVisible() + { + final Marker first_base = getSelection().getStartBaseOfSelection(); + final GotoEvent new_event = new GotoEvent(this, first_base); + getGotoEventSource().sendGotoEvent(new_event); + } + + /** + * Return an object that implements the GotoEventSource interface, and is + * the controlling object for Goto events associated with this object. + **/ + public GotoEventSource getGotoEventSource() + { + return goto_event_source; + } + + /** + * Return the EntryGroup object that was passed to the constructor. + **/ + public EntryGroup getEntryGroup() + { + return entry_group; + } + + /** + * Returns a Selection object containing the selected features/exons. + **/ + public Selection getSelection() + { + return selection; + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can update the File menu when the + * EntryGroup changes. + **/ + public void entryGroupChanged(final EntryGroupChangeEvent event) + { + switch(event.getType()) + { + case EntryGroupChangeEvent.ENTRY_ADDED: + case EntryGroupChangeEvent.ENTRY_DELETED: + makeFileMenu(); + break; + } + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so that we can update the File menu when an Entry + * changes. + **/ + public void entryChanged(final EntryChangeEvent event) + { + if(event.getType() == EntryChangeEvent.NAME_CHANGED) + makeFileMenu(); + } + + /* private members: */ + + /** + * This method arranges for the EntryEdit JFrame to go away. This EntryEdit + * object was created by the main program, so the main program must be the + * one to delete us. + **/ + private void entryEditFinished() + { + setVisible(false); + getEntryGroup().removeFeatureChangeListener(selection); + getEntryGroup().removeEntryChangeListener(selection); + + getEntryGroup().removeEntryGroupChangeListener(this); + getEntryGroup().removeEntryChangeListener(this); + + getEntryGroup().unref(); + dispose(); + } + + /** + * Write the default Entry in the EntryGroup to the file it came from. + **/ + private void saveDefaultEntry() + { + if(getEntryGroup().getDefaultEntry() == null) + new MessageDialog(EntryEdit.this, "There is no default entry"); + else + saveEntry(entry_group.getDefaultEntry(), true, false, true, + DocumentEntryFactory.ANY_FORMAT); + } + + /** + * Save the given entry, prompting for a file name if necessary. + * @param include_diana_extensions If true then any diana additions to + * the embl file format will be included in the output, otherwise they + * will be removed. Also possible problems that would cause an entry to + * bounce from the EMBL submission process will be flagged if this is + * true. + * @param ask_for_name If true then always prompt for a new filename, + * otherwise prompt only when the entry name is not set. + * @param keep_new_name If ask_for_name is true a file will be written with + * the new name the user selects - if keep_new_name is true as well, then + * the entry will have it's name set to the new name, otherwise it will + * be used for this save and then discarded. + * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or + * ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the + * same format it was created, otherwise it will be saved in the given + * format. + **/ + void saveEntry(final Entry entry, + final boolean include_diana_extensions, + final boolean ask_for_name, final boolean keep_new_name, + final int destination_type) + { + + if(!include_diana_extensions) + { + if(displaySaveWarnings(entry)) + return; + } + + if(destination_type != DocumentEntryFactory.ANY_FORMAT && + entry.getHeaderText() != null) + { + final YesNoDialog yes_no_dialog = + new YesNoDialog(this, "header section will be lost. continue?"); + + if(!yes_no_dialog.getResult()) + return; + } + + if(!System.getProperty("os.arch").equals("alpha")) + { + final EntryFileDialog file_dialog = new EntryFileDialog(this, + false); + + file_dialog.saveEntry(entry, include_diana_extensions, ask_for_name, + keep_new_name, destination_type); + } + else + alphaBug(entry, include_diana_extensions, ask_for_name, + keep_new_name, destination_type); + } + + + /** + * + * This routine is needed to circumvent the problem + * of opening a JFileChooser on alpha o/s only. + * + */ + private void alphaBug(Entry entry, + boolean include_diana_extensions, + boolean ask_for_name, + boolean keep_new_name, + int destination_type) + { + try + { + if(ask_for_name || entry.getName() == null) + { + JCheckBox emblHeader = new JCheckBox("Add EMBL Header", + false); + Box bdown = Box.createVerticalBox(); + JTextField fileField = new JTextField( + System.getProperty("user.dir")+ + System.getProperty("file.separator")); + fileField.selectAll(); + bdown.add(fileField); + + if(destination_type == DocumentEntryFactory.EMBL_FORMAT) + bdown.add(emblHeader); + + int n = JOptionPane.showConfirmDialog(null, bdown, + "Enter filename", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if(n == JOptionPane.CANCEL_OPTION) + return; + + if(emblHeader.isSelected()) + { + if( entry.getHeaderText() == null || + isHeaderEMBL(entry.getHeaderText()) ) + System.out.println("NO HEADER PLEASE SET"); + } + + File file = new File(fileField.getText()); + + if(file.exists()) + { + final YesNoDialog yes_no_dialog = new YesNoDialog(this, + "this file exists: " + file.getName() + + " overwrite it?"); + if(!yes_no_dialog.getResult()) + return; + } + + final MessageDialog message = new MessageDialog(this, + "saving to " + file.getName() + " ...", + false); + try + { + if(include_diana_extensions) + entry.save(file, destination_type, false); + else + entry.saveStandardOnly(file, destination_type, true); + } + catch(EntryInformationException e) + { + final YesNoDialog yes_no_dialog = new YesNoDialog(this, + "destination format can't handle all " + + "keys/qualifiers - continue?"); + if(yes_no_dialog.getResult()) + { + try + { + if(include_diana_extensions) + entry.save(file, destination_type, true); + else + entry.saveStandardOnly(file, destination_type, true); + } + catch(EntryInformationException e2) + { + throw new Error("internal error - unexpected exception: "+ e); + } + catch(IOException ioe) + { + new MessageDialog(this, "error while saving: " + ioe); + return; + } + } + else + return; + } + finally + { + if(message != null) + message.dispose(); + } + + if(keep_new_name) + entry.setName(file.getName()); + } + else + { + final MessageDialog message = new MessageDialog(this, + "saving to " + entry.getName() + " ...", + false); + try + { + if(include_diana_extensions) + entry.save(destination_type); + else + entry.saveStandardOnly(destination_type); + } + finally + { + message.dispose(); + } + } + } + catch(ReadOnlyException e) + { + new MessageDialog(this, "this entry is read only"); + return; + } + catch(IOException e) + { + new MessageDialog(this, "error while saving: " + e); + return; + } + catch(EntryInformationException e) + { + new MessageDialog(this, "error while saving: " + e); + return; + } + } + + + private boolean isHeaderEMBL(String header) + { + StringReader reader = new StringReader(header); + BufferedReader buff_reader = new BufferedReader(reader); + + try + { + if(!buff_reader.readLine().startsWith("ID")) + return false; + } + catch(IOException ioe){} + return true; + } + + /** + * Save the changes to all the Entry objects in the entry_group back to + * where the they came from. + **/ + public void saveAllEntries() + { + for(int entry_index = 0 ; + entry_index < entry_group.size() ; + ++entry_index) + saveEntry(entry_group.elementAt(entry_index), true, false, true, + DocumentEntryFactory.ANY_FORMAT); + } + + /** + * Check the given Entry for invalid EMBL features(such as CDS features + * without a stop codon) then display a FeatureListFrame list the problem + * features. + * @return true if and only if the save should be aborted. + **/ + private boolean displaySaveWarnings(final Entry entry) + { + final FeatureVector invalid_starts = entry.checkFeatureStartCodons(); + final FeatureVector invalid_stops = entry.checkFeatureStopCodons(); + final FeatureVector invalid_keys = entry.checkForNonEMBLKeys(); + final FeatureVector duplicate_features = entry.checkForEMBLDuplicates(); + final FeatureVector overlapping_cds_features = + entry.checkForOverlappingCDSs(); + final FeatureVector missing_qualifier_features = + entry.checkForMissingQualifiers(); + + // this predicate will filter out those features that aren't in the + // entry we are trying to save + final FeaturePredicate predicate = + new FeaturePredicate() + { + public boolean testPredicate(final Feature feature) + { + if(feature.getEntry() == entry) + return true; + else + return false; + } + }; + + String entry_name = entry.getName(); + + if(entry_name == null) + entry_name = "no name"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup(getEntryGroup(), predicate, + "features from " + entry_name); + + if(invalid_starts.size() + invalid_stops.size() + + invalid_keys.size() + duplicate_features.size() + + overlapping_cds_features.size() > 0) + { + + final YesNoDialog yes_no_dialog = + new YesNoDialog(this, + "warning: some features may have problems. " + + "continue with save?"); + + if(!yes_no_dialog.getResult()) + { + getSelection().clear(); + + if(invalid_starts.size() > 0) + { + getSelection().add(invalid_starts); + + ViewMenu.showBadStartCodons(this, + getSelection(), + filtered_entry_group, + getGotoEventSource(), + base_plot_group); + } + + if(invalid_stops.size() > 0) + { + getSelection().add(invalid_stops); + + ViewMenu.showBadStopCodons(this, getSelection(), + filtered_entry_group, + getGotoEventSource(), + base_plot_group); + } + + if(invalid_keys.size() > 0) + { + getSelection().add(invalid_keys); + + ViewMenu.showNonEMBLKeys(this, getSelection(), + filtered_entry_group, + getGotoEventSource(), + base_plot_group); + } + + if(duplicate_features.size() > 0) + { + getSelection().add(duplicate_features); + + ViewMenu.showDuplicatedFeatures(this, getSelection(), + filtered_entry_group, + getGotoEventSource(), + base_plot_group); + } + + if(overlapping_cds_features.size() > 0) + { + getSelection().add(overlapping_cds_features); + + ViewMenu.showOverlappingCDSs(this, getSelection(), + filtered_entry_group, + getGotoEventSource(), + base_plot_group); + } + + if(missing_qualifier_features.size() > 0) + { + getSelection().add(missing_qualifier_features); + + ViewMenu.showMissingQualifierFeatures(this, getSelection(), + filtered_entry_group, + getGotoEventSource(), + base_plot_group); + } + + return true; + } + } + + return false; + } + + /** + * Make and add the menus for this component. + **/ + private void makeMenus() + { + final Font default_font = getDefaultFont(); + + setJMenuBar(menu_bar); + makeFileMenu(); + menu_bar.add(file_menu); + + // don't add the menu if this is an applet and we have just one entry + if(Options.readWritePossible() || getEntryGroup().size() > 1) + { + JMenu entry_group_menu = new EntryGroupMenu(this, getEntryGroup()); + menu_bar.add(entry_group_menu); + } + + JMenu select_menu = new SelectMenu(this, getSelection(), + getGotoEventSource(), getEntryGroup(), + base_plot_group); + menu_bar.add(select_menu); + + ViewMenu view_menu = new ViewMenu(this, getSelection(), + getGotoEventSource(), getEntryGroup(), + base_plot_group); + menu_bar.add(view_menu); + + JMenu goto_menu = new GotoMenu(this, getSelection(), + getGotoEventSource(), getEntryGroup()); + menu_bar.add(goto_menu); + + if(Options.readWritePossible()) + { + JMenu edit_menu = new EditMenu(this, getSelection(), + getGotoEventSource(), getEntryGroup(), + base_plot_group); + menu_bar.add(edit_menu); + + AddMenu add_menu = new AddMenu(this, getSelection(), getEntryGroup(), + getGotoEventSource(), base_plot_group); + menu_bar.add(add_menu); + + JMenu write_menu = new WriteMenu(this, getSelection(), getEntryGroup()); + menu_bar.add(write_menu); + + if(Options.isUnixHost()) + { + JMenu run_menu = new RunMenu(this, getSelection()); + menu_bar.add(run_menu); + } + } + + JMenu graph_menu = new GraphMenu(this, getEntryGroup(), + base_plot_group, + feature_display); + menu_bar.add(graph_menu); + + final JMenu display_menu = new JMenu("Display"); + final JCheckBoxMenuItem show_entry_buttons_item = + new JCheckBoxMenuItem("Show Entry Buttons"); + show_entry_buttons_item.setState(true); + show_entry_buttons_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + group_display.setVisible(show_entry_buttons_item.getState()); + // XXX change to revalidate(). + validate(); + } + }); + display_menu.add(show_entry_buttons_item); + + final JCheckBoxMenuItem show_one_line = + new JCheckBoxMenuItem("Show One Line Per Entry View", false); + show_one_line.setState(one_line_per_entry_display.isVisible()); + show_one_line.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + one_line_per_entry_display.setVisible(show_one_line.getState()); + validate(); + } + }); + display_menu.add(show_one_line); + + final JCheckBoxMenuItem show_base_display_item = + new JCheckBoxMenuItem("Show Base View"); + + show_base_display_item.setState(base_display.isVisible()); + show_base_display_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + base_display.setVisible(show_base_display_item.getState()); + // XXX change to revalidate(). + validate(); + } + }); + display_menu.add(show_base_display_item); + + final JCheckBoxMenuItem show_feature_list_item = + new JCheckBoxMenuItem("Show Feature List"); + show_feature_list_item.setState(feature_list.isVisible()); + show_feature_list_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + feature_list.setVisible(show_feature_list_item.getState()); + // XXX change to revalidate(). + validate(); + } + }); + display_menu.add(show_feature_list_item); + + menu_bar.add(display_menu); + } + + + /** + * Make a new File menureplacing the current one (if any). + **/ + private void makeFileMenu() + { + file_menu.removeAll(); + + if(Options.readWritePossible()) + { + + JMenuItem popFileManager = new JMenuItem("Show File Manager ..."); + popFileManager.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + if(ArtemisMain.filemanager == null) + ArtemisMain.filemanager = new FileManager(EntryEdit.this); + else + ArtemisMain.filemanager.setVisible(true); + } + }); + file_menu.add(popFileManager); + + // only the standalone version can save or read + EntrySource filesystem_entry_source = null; + + for(int source_index = 0; source_index < entry_sources.size(); + ++source_index) + { + final EntrySource this_source = + entry_sources.elementAt(source_index); + + if(this_source.isFullEntrySource()) + continue; + + if(this_source.getSourceName().equals("Filesystem")) + filesystem_entry_source = this_source; + + String entry_source_name = this_source.getSourceName(); + String menu_item_name = null; + + if(entry_source_name.equals("Filesystem")) + menu_item_name = "Read An Entry ..."; + else + menu_item_name = "Read An Entry From " + entry_source_name + " ..."; + + final JMenuItem read_entry = new JMenuItem(menu_item_name); + + read_entry.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + readAnEntry(this_source); + } + }); + + file_menu.add(read_entry); + } + + JMenu read_features_menu = null; + + if(filesystem_entry_source != null && + entry_group != null && entry_group.size() > 0) + { + read_features_menu = new JMenu("Read Entry Into"); + file_menu.add(read_features_menu); + } + + file_menu.addSeparator(); + + final JMenuItem save_default = + new JMenuItem("Save Default Entry"); + save_default.setAccelerator(SAVE_DEFAULT_KEY); + save_default.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + saveDefaultEntry(); + } + }); + + file_menu.add(save_default); + + if(entry_group == null || entry_group.size() == 0) + { + // do nothing + } + else + { + final JMenu save_entry_menu = new JMenu("Save An Entry"); + final JMenu save_as_menu = new JMenu("Save An Entry As"); + final JMenu save_as = new JMenu("New File"); + final JMenu save_as_embl = new JMenu("EMBL Format"); + final JMenu save_as_genbank = new JMenu("GENBANK Format"); + final JMenu save_as_gff = new JMenu("GFF Format"); + final JMenu save_embl_only = new JMenu("EMBL Submission Format"); + + for(int i = 0; i < getEntryGroup().size(); ++i) + { + final Entry this_entry = getEntryGroup().elementAt(i); + String entry_name = this_entry.getName(); + + if(entry_name == null) + entry_name = "no name"; + + final ActionListener save_entry_listener = + new SaveEntryActionListener(this, this_entry); + + final JMenuItem save_entry_item = new JMenuItem(entry_name); + + save_entry_item.addActionListener(save_entry_listener); + + final ActionListener save_as_listener = + new SaveEntryAsActionListener(this, this_entry); + + final JMenuItem save_as_item = new JMenuItem(entry_name); + + save_as_item.addActionListener(save_as_listener); + + final ActionListener save_as_embl_listener = + new SaveEntryAsEMBLActionListener(this, this_entry); + + final JMenuItem save_as_embl_item = new JMenuItem(entry_name); + + save_as_embl_item.addActionListener(save_as_embl_listener); + + final ActionListener save_as_genbank_listener = + new SaveEntryAsGenbankActionListener(this, this_entry); + + final JMenuItem save_as_genbank_item = new JMenuItem(entry_name); + + save_as_genbank_item.addActionListener(save_as_genbank_listener); + + final ActionListener save_as_gff_listener = + new SaveEntryAsGFFActionListener(this, this_entry); + + final JMenuItem save_as_gff_item = new JMenuItem(entry_name); + + save_as_gff_item.addActionListener(save_as_gff_listener); + + final ActionListener save_embl_only_listener = + new SaveEntryAsSubmissionActionListener(this, this_entry); + + final JMenuItem save_embl_only_item = new JMenuItem(entry_name); + + save_embl_only_item.addActionListener(save_embl_only_listener); + + if(read_features_menu != null) + { + final ActionListener read_into_listener = + new ReadFeaturesActionListener(this, filesystem_entry_source, + this_entry); + + final JMenuItem read_into_item = new JMenuItem(entry_name); + read_into_item.addActionListener(read_into_listener); + read_features_menu.add(read_into_item); + } + + save_entry_menu.add(save_entry_item); + + save_as.add(save_as_item); + save_as_embl.add(save_as_embl_item); + save_as_genbank.add(save_as_genbank_item); + save_as_gff.add(save_as_gff_item); + save_embl_only.add(save_embl_only_item); + } + + save_as_menu.add(save_as); + save_as_menu.add(save_as_embl); + save_as_menu.add(save_as_genbank); + save_as_menu.add(save_as_gff); + save_as_menu.addSeparator(); + save_as_menu.add(save_embl_only); + + file_menu.add(save_entry_menu); + file_menu.add(save_as_menu); + } + + final JMenuItem save_all = new JMenuItem("Save All Entries"); + save_all.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + saveAllEntries(); + } + }); + + file_menu.add(save_all); + file_menu.addSeparator(); + } + + final JMenuItem clone_entry_edit = new JMenuItem("Clone This Window"); + clone_entry_edit.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + new EntryEdit(getEntryGroup()).show(); + } + }); + + file_menu.add(clone_entry_edit); + file_menu.addSeparator(); + + + final JMenuItem close = new JMenuItem("Close"); + close.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + closeEntryEdit(); + } + }); + + file_menu.add(close); + } + + /** + * Read an entry + **/ + private void readAnEntry(final EntrySource this_source) + { + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + + SwingWorker entryWorker = new SwingWorker() + { + public Object construct() + { + try + { + final Entry new_entry = this_source.getEntry(entry_group.getBases(), + progress_thread, true); + if(new_entry != null) + getEntryGroup().add(new_entry); + } + catch(final OutOfRangeException e) + { + new MessageDialog(EntryEdit.this, + "read failed: one of the features " + + "in the entry has an out of " + + "range location: " + + e.getMessage()); + } + catch(final IOException e) + { + new MessageDialog(EntryEdit.this, + "read failed due to an IO error: " + + e.getMessage()); + } + return null; + } + + public void finished() + { + if(progress_thread !=null) + progress_thread.finished(); + } + }; + entryWorker.start(); + } + + /** + * Read an entry + **/ + private void readAnEntryFromFile(final File file) + { + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + progress_thread.start(); + + SwingWorker entryWorker = new SwingWorker() + { + public Object construct() + { + try + { + EntryInformation new_entry_information = + new SimpleEntryInformation(Options.getArtemisEntryInformation()); + + final Entry new_entry = new Entry(entry_group.getBases(), + EntryFileDialog.getEntryFromFile(null, + new FileDocument(file), + new_entry_information, false)); + + if(new_entry != null) + getEntryGroup().add(new_entry); + } + catch(final OutOfRangeException e) + { + new MessageDialog(EntryEdit.this, + "read failed: one of the features " + + "in the entry has an out of " + + "range location: " + + e.getMessage()); + } + return null; + } + + public void finished() + { + if(progress_thread !=null) + progress_thread.finished(); + } + }; + entryWorker.start(); + } + + /** + * Return the current default font(from Diana.options). + **/ + private Font getDefaultFont() + { + return Options.getOptions().getFont(); + } + + // DropTargetListener methods + protected static Border dropBorder = new BevelBorder(BevelBorder.LOWERED); + public void drop(DropTargetDropEvent e) + { + Transferable t = e.getTransferable(); + try + { + if(t.isDataFlavorSupported(FileNode.FILENODE)) + { + FileNode fn = (FileNode)t.getTransferData(FileNode.FILENODE); + readAnEntryFromFile(fn.getFile()); + } + else + e.rejectDrop(); + } + catch(UnsupportedFlavorException ufe) + { + ufe.printStackTrace(); + } + catch(IOException ioe) + { + ioe.printStackTrace(); + } + finally + { + ((JComponent)getContentPane()).setBorder(null); + } + } + + public void dragExit(DropTargetEvent e) + { + ((JComponent)getContentPane()).setBorder(null); + } + public void dropActionChanged(DropTargetDragEvent e) {} + + public void dragOver(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(FileNode.FILENODE)) + { + Point ploc = e.getLocation(); + if(this.contains(ploc.x,ploc.y)) + { + ((JComponent)getContentPane()).setBorder(dropBorder); + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + } + else + e.rejectDrag(); + } + } + + public void dragEnter(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(FileNode.FILENODE)) + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + } + +} + +/** + * This is an EntryActionListener that will get an entry from entry_source + * and then copy them to destination_entry when actionPerformed() is called. + **/ +class ReadFeaturesActionListener extends EntryActionListener +{ + final EntrySource entry_source; + + ReadFeaturesActionListener(final EntryEdit entry_edit, + final EntrySource entry_source, + final Entry destination_entry) + { + super(entry_edit, destination_entry); + this.entry_source = entry_source; + } + + public void actionPerformed(final ActionEvent event) + { + try + { + if(getEntry().isReadOnly()) + { + final String message = + "the default entry is read only - cannot continue"; + new MessageDialog(getEntryEdit(), message); + } + + final Entry source_entry = + entry_source.getEntry(getEntryEdit().getEntryGroup().getBases(), + true); + + for(int i = 0; i < source_entry.getFeatureCount(); ++i) + { + final Feature this_feature = source_entry.getFeature(i); + try + { + this_feature.copyTo(getEntry()); + } + catch(OutOfRangeException e) + { + throw new Error("internal error - unexpected exception: " + e); + } + catch(EntryInformationException e) + { + final String message = + "couldn't move one of the features(" + + this_feature.getIDString() + "): " + e.getMessage(); + new MessageDialog(getEntryEdit(), message); + } + catch(ReadOnlyException e) + { + final String message = + "the default entry is read only - cannot continue"; + new MessageDialog(getEntryEdit(), message); + return; + } + } + } + catch(OutOfRangeException e) + { + new MessageDialog(getEntryEdit(), + "read failed: one of the features in " + + "the source entry has an out of range location"); + } + catch(IOException e) + { + new MessageDialog(getEntryEdit(), + "read failed due to an IO error: " + + e.getMessage()); + } + catch(NullPointerException e) + { + new MessageDialog(getEntryEdit(), + "read failed due to a null pointer error: " + + e.getMessage()); + } + + } + +} + +/** + * This is an EntryActionListener that will call saveEntry() when + * actionPerformed() is called. + **/ +class SaveEntryActionListener extends EntryActionListener +{ + SaveEntryActionListener(final EntryEdit entry_edit, + final Entry entry) + { + super(entry_edit, entry); + } + + public void actionPerformed(final ActionEvent event) + { + getEntryEdit().saveEntry(getEntry(), true, false, true, + DocumentEntryFactory.ANY_FORMAT); + } +} + +/** + * This is an EntryActionListener that will call saveEntry() when + * actionPerformed() is called. + **/ +class SaveEntryAsActionListener extends EntryActionListener +{ + SaveEntryAsActionListener(final EntryEdit entry_edit, + final Entry entry) + { + super(entry_edit, entry); + } + + public void actionPerformed(final ActionEvent event) + { + getEntryEdit().saveEntry(getEntry(), true, true, true, + DocumentEntryFactory.ANY_FORMAT); + } +} + +/** + * This is an EntryActionListener that will call saveEntry() when + * actionPerformed() is called. The output file type will be EMBL. + **/ +class SaveEntryAsEMBLActionListener extends EntryActionListener +{ + SaveEntryAsEMBLActionListener(final EntryEdit entry_edit, + final Entry entry) + { + super(entry_edit, entry); + } + + public void actionPerformed(final ActionEvent event) + { + getEntryEdit().saveEntry(getEntry(), true, true, false, + DocumentEntryFactory.EMBL_FORMAT); + } +} + +/** + * This is an EntryActionListener that will call saveEntry() when + * actionPerformed() is called. The output file type will be GENBANK. + **/ +class SaveEntryAsGenbankActionListener extends EntryActionListener +{ + SaveEntryAsGenbankActionListener(final EntryEdit entry_edit, + final Entry entry) + { + super(entry_edit, entry); + } + + public void actionPerformed(final ActionEvent event) + { + getEntryEdit().saveEntry(getEntry(), true, true, false, + DocumentEntryFactory.GENBANK_FORMAT); + } +} + +/** + * This is an EntryActionListener that will call saveEntry() when + * actionPerformed() is called. The output file type will be GFF, with the + * sequence(if any) in FASTA format. + **/ +class SaveEntryAsGFFActionListener extends EntryActionListener +{ + SaveEntryAsGFFActionListener(final EntryEdit entry_edit, + final Entry entry) + { + super(entry_edit, entry); + } + + public void actionPerformed(final ActionEvent event) + { + getEntryEdit().saveEntry(getEntry(), true, true, false, + DocumentEntryFactory.GFF_FORMAT); + } +} + +/** + * This is an EntryActionListener that will call saveEntry() when + * actionPerformed() is called. + **/ +class SaveEntryAsSubmissionActionListener extends EntryActionListener +{ + SaveEntryAsSubmissionActionListener(final EntryEdit entry_edit, + final Entry entry) + { + super(entry_edit, entry); + } + + public void actionPerformed(final ActionEvent event) + { + getEntryEdit().saveEntry(getEntry(), false, true, false, + DocumentEntryFactory.ANY_FORMAT); + } +} diff --git a/uk/ac/sanger/artemis/components/EntryEditVector.java b/uk/ac/sanger/artemis/components/EntryEditVector.java new file mode 100644 index 000000000..68421296a --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryEditVector.java @@ -0,0 +1,92 @@ +/* EntryEditVector.java + * + * created: Sat Oct 17 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryEditVector.java,v 1.1 2004-06-09 09:46:27 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.util.Vector; + +/** + * This class implements a Vector of EntryEdit objects. + * + * @author Kim Rutherford + * @version $Id: EntryEditVector.java,v 1.1 2004-06-09 09:46:27 tjc Exp $ + * + **/ + +public class EntryEditVector { + /** + * Create a new, entry EntryEditVector. + **/ + public EntryEditVector () { + + } + + /** + * Performs the same function as Vector.addElement () + */ + public void addElement (EntryEdit node) { + vector.addElement (node); + } + + /** + * Performs the same function as Vector.elementAt () + */ + public EntryEdit elementAt (int index) { + return (EntryEdit) vector.elementAt (index); + } + +// /** +// * Performs the same function as Vector.lastElement () +// **/ +// public Feature lastElement () { +// return (Feature) vector.lastElement (); +// } + + /** + * Performs the same function as Vector.removeElement () + **/ + public boolean removeElement (EntryEdit entry_edit) { + return vector.removeElement (entry_edit); + } + + /** + * Performs the same function as Vector.removeElement () + **/ + public int indexOf (EntryEdit entry_edit) { + return vector.indexOf (entry_edit); + } + + /** + * Performs the same function as Vector.size () + */ + public int size () { + return vector.size (); + } + + /** + * Storage for EntryEdit objects. + */ + final private Vector vector = new Vector (); +} diff --git a/uk/ac/sanger/artemis/components/EntryFileDialog.java b/uk/ac/sanger/artemis/components/EntryFileDialog.java new file mode 100644 index 000000000..86c1e9d1a --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryFileDialog.java @@ -0,0 +1,463 @@ +/* EntryFileDialog.java + * + * created: Mon Dec 7 1998 + * + * This file is part of Artemis + * + * Copyright(C) 1998-2003 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/EntryFileDialog.java,v 1.1 2004-06-09 09:46:28 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.Entry; +import uk.ac.sanger.artemis.io.DocumentEntryFactory; +import uk.ac.sanger.artemis.io.ReadFormatException; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.EntryInformationException; + +import java.io.File; +import java.io.IOException; +import java.io.FileNotFoundException; +import javax.swing.*; + +/** + * This class is a JFileChooser that can read EMBL Entry objects. + * + * @author Kim Rutherford + * @version $Id: EntryFileDialog.java,v 1.1 2004-06-09 09:46:28 tjc Exp $ + **/ + +public class EntryFileDialog extends StickyFileChooser +{ + + /** JFrame reference that was passed to the constructor. */ + private JFrame owner = null; + + /** + * Create a new EntryFileDialog component. + * @param owner The component where this dialog was created. + * @param sequence_only If true default to showing only file that have + * suffixes that suggest that the files contain sequence(eg. .embl, + * .seq, .dna). Of false show all files that can be read. + **/ + public EntryFileDialog(final JFrame owner, + final boolean show_sequence_only) + { + super(); + this.owner = owner; + + setFileSelectionMode(JFileChooser.FILES_ONLY); + setMultiSelectionEnabled(false); + + final StringVector sequence_suffixes = + Options.getOptions().getOptionValues("sequence_file_suffixes"); + + final StringVector feature_suffixes = + Options.getOptions().getOptionValues("feature_file_suffixes"); + + final javax.swing.filechooser.FileFilter artemis_filter = + new javax.swing.filechooser.FileFilter() + { + public boolean accept(final File file) + { + if(file.isDirectory()) + return true; + + for(int i = 0; i<sequence_suffixes.size(); ++i) + { + final String this_suffix = sequence_suffixes.elementAt(i); + + if(file.getName().endsWith("." + this_suffix) || + file.getName().endsWith("." + this_suffix + ".gz")) + return true; + } + + for(int i = 0; i<feature_suffixes.size(); ++i) + { + final String this_suffix = feature_suffixes.elementAt(i); + + if(file.getName().endsWith("." + this_suffix) || + file.getName().endsWith("." + this_suffix + ".gz")) + return true; + } + return false; + } + + public String getDescription() + { + return "Artemis files"; + } + }; + + final javax.swing.filechooser.FileFilter feature_filter = + new javax.swing.filechooser.FileFilter() + { + public boolean accept(final File file) + { + if(file.isDirectory()) + return true; + + for(int i = 0 ; i < feature_suffixes.size() ; ++i) + { + final String this_suffix = feature_suffixes.elementAt(i); + + if(file.getName().endsWith("." + this_suffix) || + file.getName().endsWith("." + this_suffix + ".gz")) + return true; + } + + return false; + } + + public String getDescription() + { + return "Feature files"; + } + }; + + final javax.swing.filechooser.FileFilter sequence_filter = + new javax.swing.filechooser.FileFilter() + { + public boolean accept(final File file) + { + if(file.isDirectory()) + return true; + + for(int i = 0 ; i<sequence_suffixes.size() ; ++i) + { + final String this_suffix = sequence_suffixes.elementAt(i); + + if(file.getName().endsWith("." + this_suffix) || + file.getName().endsWith("." + this_suffix + ".gz")) + return true; + } + + return false; + } + + public String getDescription() + { + return "Sequence files"; + } + }; + + addChoosableFileFilter(artemis_filter); + addChoosableFileFilter(feature_filter); + addChoosableFileFilter(sequence_filter); + + if(show_sequence_only) + setFileFilter(sequence_filter); + else + setFileFilter(artemis_filter); + } + + /** + * Return an uk.ac.sanger.artemis.io.Entry object representing the file + * the user has selected with the dialog or null if the read failed for any + * reason. + * @param entry_information The EntryInformation to use when reading. This + * supplies the list of valid keys and qualifiers. If a key or qualifier + * is read that is incompatible with this EntryInformation object the + * EntryInformation will be changed to cope. + * @param listener The object to which InputStreamProgressEvents will be + * send while reading. + * @exception EntryInformationException Thrown if an Entry using the given + * EntryInformation object cannot contain the Key, Qualifier or + * Key/Qualifier combination of one of the features in the Document. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + **/ + public Entry getEntry(final EntryInformation entry_information, + final InputStreamProgressListener listener, + final ProgressThread progress_thread, + final boolean show_progress) + { + setDialogTitle("Select a file ..."); + setDialogType(JFileChooser.OPEN_DIALOG); + SecurityManager sm = System.getSecurityManager(); + System.setSecurityManager(null); + + final int status = showOpenDialog(owner); + + System.setSecurityManager(sm); + + if(status != JFileChooser.APPROVE_OPTION || + getSelectedFile() == null) + return null; + + if(progress_thread != null) + progress_thread.start(); + + final File file = new File(getCurrentDirectory(), + getSelectedFile().getName()); + + return getEntryFromFile(owner, new FileDocument(file), + entry_information, show_progress); + } + + /** + * This exists only to get around a bug in the 1.1/1.2 code generator, + * which generates unverifiable code. + * @param frame Used when creating MessageDialog components. + * @param file_document The Document to read the Entry from(should be made + * from entry_file). + * @param entry_information The EntryInformation to use when reading. This + * supplies the list of valid keys and qualifiers. If a key or qualifier + * is read that is incompatible with this EntryInformation object the + * EntryInformation will be changed to cope. + **/ + private static Entry getEntryFromFileHelper(final JFrame frame, + final Document file_document, + final EntryInformation entry_information) + throws ReadFormatException, IOException + { + + final LogReadListener read_event_logger = + new LogReadListener(file_document.getName()); + + final Entry new_entry; + + try + { + new_entry = + DocumentEntryFactory.makeDocumentEntry(entry_information, + file_document,read_event_logger); + } + catch(EntryInformationException e) + { + throw new Error("internal error - unexpected exception: " + e); + } + + if(read_event_logger.seenMessage()) + { + final YesNoDialog yes_no_dialog = new YesNoDialog(frame, + "there were warnings while reading - view now?"); + + if(yes_no_dialog.getResult()) + Splash.showLog(); + } + + return new_entry; + } + + /** + * Read and return an Entry from the given File. + * @param frame Used when creating MessageDialog components. + * @param entry_file The file to read the Entry from. + * @param entry_information The EntryInformation to use when reading. This + * supplies the list of valid keys and qualifiers. If a key or qualifier + * is read that is incompatible with this EntryInformation object the + * EntryInformation will be changed to cope. + * @param listener The object to which InputStreamProgressEvents will be + * send while reading. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + **/ + public static Entry getEntryFromFile(final JFrame frame, + final Document entry_document, + final EntryInformation entry_information, + final boolean show_progress) + { + InputStreamProgressDialog progress_dialog = null; + + if(show_progress) { +// XXX FIXME + +// This doesn't work because getEntryFromFile() is called from the Swing +// thread so the Dialog never gets updated + +// progress_dialog = +// new InputStreamProgressDialog(frame, "Reading ...", +// "Reading from " + +// entry_document.getName(), false); +// final InputStreamProgressListener listener = +// progress_dialog.getInputStreamProgressListener(); + +// entry_document.addInputStreamProgressListener(listener); + } + + try + { + return getEntryFromFileHelper(frame, entry_document, + entry_information); + } + catch(ReadFormatException e) + { + final String message = + "failed to open " + entry_document.getName() + ": " + + e.getMessage() +(e.getLineNumber() > 1 ? + " at line: " + e.getLineNumber() : + ""); + System.out.println(message); + new MessageDialog(frame, message); + } + catch(FileNotFoundException e) + { + final String message = + "failed to open " + entry_document.getName() + ": file not found"; + new MessageDialog(frame, message); + } + catch(IOException e) + { + final String message = + "failed to open " + entry_document.getName() + ": " + e.getMessage(); + new MessageDialog(frame, message); + } + finally + { +// if(progress_dialog != null) +// progress_dialog.dispose(); + } + return null; + } + + /** + * Save the given entry, prompting for a file name if necessary. + * @param include_diana_extensions If true then the any diana additions to + * the embl file format will be included in the output, otherwise they + * will be removed. Also possible problems that would cause an entry to + * bounce from the EMBL submission process will be flagged if this is + * true. + * @param ask_for_name If true then always prompt for a new filename, + * otherwise prompt only when the entry name is not set. + * @param keep_new_name If ask_for_name is true a file will be written with + * the new name the user selects - if keep_new_name is true as well, then + * the entry will have it's name set to the new name, otherwise it will + * be used for this save and then discarded. + * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or + * ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the + * same format it was created, otherwise it will be saved in the given + * format. + **/ + void saveEntry(final uk.ac.sanger.artemis.Entry entry, + final boolean include_diana_extensions, + final boolean ask_for_name, + final boolean keep_new_name, + final int destination_type) + { + try + { + if(ask_for_name || entry.getName() == null) + { + JCheckBox emblHeader = new JCheckBox("Add EMBL Header", + false); + setDialogTitle("Save to ..."); + setDialogType(JFileChooser.SAVE_DIALOG); + +// if(destination_type == DocumentEntryFactory.EMBL_FORMAT) +// setAccessory(emblHeader); + int status = showSaveDialog(owner); + + if(status != JFileChooser.APPROVE_OPTION || + getSelectedFile() == null) + return; + + File file = new File(getCurrentDirectory(), + getSelectedFile().getName()); + + if(file.exists()) + { + final YesNoDialog yes_no_dialog = new YesNoDialog(owner, + "this file exists: " + file.getName() + + " overwrite it?"); + + if(!yes_no_dialog.getResult()) + return; + } + + final MessageDialog message = new MessageDialog(owner, + "saving to " + file.getName() + " ...", + false); + try + { + if(include_diana_extensions) + entry.save(file, destination_type, false); + else + entry.saveStandardOnly(file, destination_type, true); + } + catch(EntryInformationException e) + { + final YesNoDialog yes_no_dialog = new YesNoDialog(owner, + "destination format can't handle all " + + "keys/qualifiers - continue?"); + if(yes_no_dialog.getResult()) + { + try + { + if(include_diana_extensions) + entry.save(file, destination_type, true); + else + entry.saveStandardOnly(file, destination_type, true); + } + catch(EntryInformationException e2) + { + throw new Error("internal error - unexpected exception: "+ e); + } + } + else + return; + } + finally + { + if(message != null) + message.dispose(); + } + + if(keep_new_name) + entry.setName(file.getName()); + } + else + { + final MessageDialog message = new MessageDialog(owner, + "saving to " + entry.getName() + " ...", + false); + try + { + if(include_diana_extensions) + entry.save(destination_type); + else + entry.saveStandardOnly(destination_type); + } + finally + { + message.dispose(); + } + } + } + catch(ReadOnlyException e) + { + new MessageDialog(owner, "this entry is read only"); + return; + } + catch(IOException e) + { + new MessageDialog(owner, "error while saving: " + e); + return; + } + catch(EntryInformationException e) + { + new MessageDialog(owner, "error while saving: " + e); + return; + } + } + +} + diff --git a/uk/ac/sanger/artemis/components/EntryGroupDisplay.java b/uk/ac/sanger/artemis/components/EntryGroupDisplay.java new file mode 100644 index 000000000..a37b6f6cf --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryGroupDisplay.java @@ -0,0 +1,240 @@ +/* EntryGroupDisplay.java + * + * created: Mon Dec 7 1998 + * + * This file is part of Artemis + * + * Copyright(C) 1998,1999,2000,2002 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/EntryGroupDisplay.java,v 1.1 2004-06-09 09:46:29 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import java.awt.FlowLayout; +import java.awt.Color; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * This component allows the user to change the "active" setting of the + * objects in an EntryGroup. + * + * @author Kim Rutherford + * @version $Id: EntryGroupDisplay.java,v 1.1 2004-06-09 09:46:29 tjc Exp $ + **/ + +public class EntryGroupDisplay extends JPanel + implements EntryGroupChangeListener, EntryChangeListener +{ + final private static Color background_colour = new Color(200, 200, 200); + + /** + * This is a reference to the EntryEdit component that created this + * EntryGroupDisplay. We need this reference so that we can watch the + * entry group. + **/ + private EntryEdit owning_component; + + /** + * A vector containing the Entry objects that this EntryEdit object knows + * about. This reference is obtained from owning_component. + **/ + private EntryGroup entry_group; + + /** + * A vector containing one JCheckBox or Label for each Entry in the + * EntryGroup object. + **/ + private Vector entry_components = new Vector(); + + /** + * A label containing the message "Entry:". + **/ + private JLabel label; + + /** + * Create a new EntryGroupDisplay object. + * @param owning_component The EntryEdit object that this EntryGroupDisplay + * component is in. + **/ + public EntryGroupDisplay(final EntryEdit owning_component) + { + this.owning_component = owning_component; + this.entry_group = owning_component.getEntryGroup(); + + entry_group.addEntryGroupChangeListener(this); + entry_group.addEntryChangeListener(this); + + final FlowLayout flow_layout = new FlowLayout(FlowLayout.LEFT); + + label = new JLabel("Entry: "); + + setLayout(flow_layout); + refreshButtons(); + + setBackground(background_colour); + } + + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can update the display if entries + * are added or deleted. + **/ + public void entryGroupChanged(final EntryGroupChangeEvent event) + { + switch(event.getType()) + { + case EntryGroupChangeEvent.ENTRY_ADDED: + case EntryGroupChangeEvent.ENTRY_DELETED: + refreshButtons(); + break; + case EntryGroupChangeEvent.ENTRY_INACTIVE: + case EntryGroupChangeEvent.ENTRY_ACTIVE: + updateActive(); + break; + case EntryGroupChangeEvent.NEW_DEFAULT_ENTRY: + highlightDefaultEntry(event); + break; + } + } + + /** + * Implementation of the EntryChangeListener interface. + **/ + public void entryChanged(final EntryChangeEvent event) + { + if(event.getType() == EntryChangeEvent.NAME_CHANGED) + refreshButtons(); + } + + /** + * Remove and then recreate the Buttons to the reflect the current contents + * of the EntryGroup. + **/ + private void refreshButtons() + { + removeAll(); + add(label); + + entry_components = new Vector(); + + if(entry_group == null) + return; + else + { + for(int i = 0; i < entry_group.size(); ++i) + add(entry_group.elementAt(i)); + } + + doLayout(); + } + + /** + * Update the buttons to reflect the current state of the EntryGroup. + **/ + private void updateActive() + { + for(int i = 0 ; i < entry_group.size() ; ++i) + { + final JCheckBox menu_item = (JCheckBox)entry_components.elementAt(i); + menu_item.setSelected(entry_group.isActive(entry_group.elementAt(i))); + } + } + + /** + * Add a JLabel or JCheckBox for the given Entry to this component. + **/ + private void add(final Entry entry) + { + final JCheckBox new_component; + String entry_name = entry.getName(); + + if(entry_name == null) + entry_name = "no name"; + + new_component = new JCheckBox(entry_name, entry_group.isActive(entry)); + + setEntryHighlight(entry, new_component); + + new_component.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + final int button_index = + entry_components.indexOf(event.getSource()); + + if(event.getStateChange() == ItemEvent.SELECTED) + entry_group.setIsActive(button_index, true); + else + entry_group.setIsActive(button_index, false); + } + }); + + new_component.addMouseListener(new MouseAdapter() + { + /** + * Listen for mouse press events so that we can change the default + * Entry when the popup trigger is pressed. + **/ + public void mousePressed(MouseEvent event) + { + if(event.isPopupTrigger()) + entry_group.setDefaultEntry(entry); + } + }); + + entry_components.addElement(new_component); + add(new_component); + } + + /** + * Given a EntryGroupChangeEvent, this method will highlight the new + * default entry + **/ + private void highlightDefaultEntry(final EntryGroupChangeEvent event) + { + final EntryGroup entry_group = owning_component.getEntryGroup(); + + for(int i = 0 ; i < entry_group.size() ; ++i) + { + final JCheckBox check_box =(JCheckBox) entry_components.elementAt(i); + + setEntryHighlight(entry_group.elementAt(i), check_box); + } + } + + /** + * Highlight the given JCheckBox/Entry appropriately. The default Entry + * will look different to the others + **/ + private void setEntryHighlight(final Entry entry, + final JCheckBox component) + { + final String label = component.getText(); + + if(entry_group.getDefaultEntry() == entry) + component.setBackground(Color.yellow); + else + component.setBackground(background_colour); + } + +} diff --git a/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java b/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java new file mode 100644 index 000000000..960ab6112 --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java @@ -0,0 +1,536 @@ +/* EntryGroupInfoDisplay.java + * + * created: Fri Mar 12 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java,v 1.1 2004-06-09 09:46:30 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.Location; +import uk.ac.sanger.artemis.io.FuzzyRange; +import uk.ac.sanger.artemis.io.RangeVector; +import uk.ac.sanger.artemis.io.InvalidRelationException; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import java.util.Hashtable; +import java.util.Enumeration; +import java.awt.event.*; + +import javax.swing.JFrame; + +/** + * This component will show general information about an EntryGroup. It will + * show the sequence length, GC content and a summary of the active entries. + * + * @author Kim Rutherford + * @version $Id: EntryGroupInfoDisplay.java,v 1.1 2004-06-09 09:46:30 tjc Exp $ + **/ + +public class EntryGroupInfoDisplay + implements FeatureChangeListener, EntryChangeListener, + EntryGroupChangeListener, SequenceChangeListener { + /** + * Create a new EntryGroupInfoDisplay object to summarize all features on + * both strands of the given EntryGroup. + * @param parent_frame The reference of the parent JFrame. + * @param entry_group The EntryGroup to view. + **/ + public EntryGroupInfoDisplay (final JFrame parent_frame, + final EntryGroup entry_group) { + this (parent_frame, entry_group, BOTH); + } + + /** + * Create a new EntryGroupInfoDisplay object to summarize the given + * EntryGroup. + * @param parent_frame The reference of the parent JFrame. + * @param entry_group The EntryGroup to view. + * @param strand_flag Indicates which strand to summarize. BOTH means + * summarize all features on both strands. FORWARD means summarize + * forward strand only. REVERSE means summarize reverse strand only. + **/ + public EntryGroupInfoDisplay (final JFrame parent_frame, + EntryGroup entry_group, + final int strand_flag) { + this.strand_flag = strand_flag; + this.parent_frame = parent_frame; + this.entry_group = entry_group; + + if (strand_flag == FORWARD) { + final FeaturePredicate forward_feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature test_feature) { + return test_feature.isForwardFeature (); + } + }; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, forward_feature_predicate, + "forward strand features"); + + entry_group = filtered_entry_group; + } else { + if (strand_flag == REVERSE) { + final FeaturePredicate reverse_feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature test_feature) { + return !test_feature.isForwardFeature (); + } + }; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, reverse_feature_predicate, + "reverse strand features"); + + entry_group = filtered_entry_group; + } else { + if (strand_flag != BOTH) { + throw new Error ("internal error - illegal argument"); + } + } + } + + file_viewer = new FileViewer (getFrameName ()); + + updateView (); + + entry_group.addEntryChangeListener (this); + entry_group.addFeatureChangeListener (this); + entry_group.addEntryGroupChangeListener (this); + entry_group.getBases ().addSequenceChangeListener (this, + Bases.MAX_PRIORITY); + + file_viewer.addWindowListener (new WindowAdapter () { + public void windowClosed (WindowEvent event) { + stopListening (); + } + }); + } + + /** + * Return the String that should be used for the title of the Overview Frame + **/ + private String getFrameName () { + if (entry_group instanceof FilteredEntryGroup) { + final FilteredEntryGroup filtered_entry_group = + (FilteredEntryGroup) entry_group; + + final String filter_name = filtered_entry_group.getFilterName (); + + if (filter_name == null) { + return "Artemis overview of: " + parent_frame.getTitle (); + } else { + return "Artemis overview of: " + parent_frame.getTitle () + + " (" + filter_name + ")"; + } + } else { + final StringBuffer buffer = new StringBuffer ("Artemis Overview"); + + if (entry_group.size () > 0) { + + buffer.append (" of:"); + + for (int i = 0 ; i < entry_group.size () ; ++i) { + final Entry this_entry = entry_group.elementAt (i); + + if (this_entry.getName () == null) { + buffer.append (" [no name]"); + } else { + buffer.append (" " + this_entry.getName ()); + } + } + } + + return buffer.toString (); + } + } + + /** + * Use the forward strand when summarizing. + **/ + final static public int FORWARD = Bases.FORWARD; + + /** + * Use the reverse strand when summarizing. + **/ + final static public int REVERSE = Bases.REVERSE; + + /** + * Summarize both strands. + **/ + final static public int BOTH = 3; + + /** + * Remove this object as a selection change listener. + **/ + private void stopListening () { + entry_group.removeEntryChangeListener (this); + entry_group.removeEntryGroupChangeListener (this); + entry_group.removeFeatureChangeListener (this); + entry_group.getBases ().removeSequenceChangeListener (this); + } + + /** + * Implementation of the FeatureChangeListener interface. We listen to + * FeatureChange events so that we can update the display if qualifiers + * change. + **/ + public void featureChanged (final FeatureChangeEvent event) { + updateView (); + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can update the display if entries + * are added, deleted, activated or deactivated. + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + if (event.getType () == EntryGroupChangeEvent.DONE_GONE) { + stopListening (); + file_viewer.dispose (); + } else { + updateView (); + } + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so that we can update the display if features are + * added or deleted. + **/ + public void entryChanged (final EntryChangeEvent event) { + updateView (); + } + + /** + * Implementation of the SequenceChangeListener interface. We listen to + * SequenceChange events so that we can update the display if the sequence + * changes. + **/ + public void sequenceChanged (final SequenceChangeEvent event) { + updateView (); + } + + /** + * Update the FileViewer component to reflect the current state of the + * EntryGroup. + **/ + private void updateView () { + final String frame_name = getFrameName (); + + file_viewer.setTitle (frame_name); + + final StringBuffer buffer = new StringBuffer (); + + buffer.append (frame_name + "\n\n"); + + buffer.append ("Number of bases: " + + entry_group.getSequenceLength () + "\n"); + buffer.append ("Number of features in the active entries: " + + entry_group.getAllFeaturesCount () + "\n\n"); + + final Bases entry_group_bases = entry_group.getBases (); + + // cds_count - gene_count = pseudo gene count + int gene_count = 0; + int cds_count = 0; + int spliced_gene_count = 0; + int spliced_gene_bases_count = 0; + StringBuffer gene_bases_buffer = new StringBuffer (); + int gene_bases_count_with_introns = 0; + int cds_bases_count = 0; + int exon_count = 0; + int intron_count = 0; + int partial_count = 0; + + final Hashtable table = new Hashtable (); + + final FeatureEnumeration feature_enumerator = entry_group.features (); + + while (feature_enumerator.hasMoreFeatures ()) { + final Feature this_feature = feature_enumerator.nextFeature (); + + final Key key = this_feature.getKey (); + final String key_string = key.toString (); + + final Location location = this_feature.getLocation (); + + final RangeVector ranges = location.getRanges (); + + for (int i = 0 ; i < ranges.size () ; ++i) { + if (ranges.elementAt (i) instanceof FuzzyRange) { + ++partial_count; + } + } + + try { + String colour = this_feature.getValueOfQualifier ("colour"); + + if (colour == null || colour.length () == 0) { + colour = "no colour"; + } + + if (table.containsKey (key_string)) { + final Hashtable colour_table = (Hashtable) table.get (key_string); + + final Integer colour_value = (Integer) colour_table.get (colour); + + if (colour_value == null) { + colour_table.put (colour, new Integer (1)); + } else { + final int old_value = ((Integer) colour_value).intValue (); + + colour_table.put (colour, new Integer (old_value + 1)); + } + } else { + final Hashtable colour_table = new Hashtable (); + colour_table.put (colour, new Integer (1)); + table.put (key_string, colour_table); + } + } catch (InvalidRelationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + if (this_feature.isCDS ()) { + cds_count++; + + cds_bases_count += this_feature.getBaseCount (); + + final Qualifier pseudo_qualifier; + + try { + pseudo_qualifier = this_feature.getQualifierByName ("pseudo"); + } catch (InvalidRelationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + if (pseudo_qualifier == null) { + ++gene_count; + + final FeatureSegmentVector segments = this_feature.getSegments (); + + if (segments.size () > 1) { + ++spliced_gene_count; + spliced_gene_bases_count += this_feature.getBaseCount (); + intron_count += segments.size () - 1; + } + + exon_count += segments.size (); + + gene_bases_buffer.append (this_feature.getBases ()); + + gene_bases_count_with_introns += + this_feature.getRawLastBase () - + this_feature.getRawFirstBase () + 1; + } + } + } + + final String gene_bases = gene_bases_buffer.toString (); + final int gene_bases_count = gene_bases.length (); + + final int pseudo_gene_count = cds_count - gene_count; + final int pseudo_gene_bases_count = cds_bases_count - gene_bases_count; + + if (cds_count > 0) { + if (gene_count > 0) { + buffer.append ("Genes (CDS features without a /pseudo qualifier):\n"); + + final int non_spliced_gene_count = gene_count - spliced_gene_count; + final int non_spliced_gene_bases_count = + gene_bases_count - spliced_gene_bases_count; + + if (spliced_gene_count > 0) { + buffer.append (" spliced:\n"); + buffer.append (" count: " + spliced_gene_count + "\n"); + buffer.append (" bases: " + spliced_gene_bases_count + "\n"); + buffer.append (" introns: " + intron_count + "\n"); + } + + if (non_spliced_gene_count > 0) { + buffer.append (" non-spliced:\n"); + buffer.append (" count: " + non_spliced_gene_count + "\n"); + buffer.append (" bases: " + non_spliced_gene_bases_count + + "\n"); + } + + buffer.append (" all:\n"); + buffer.append (" count: " + gene_count + "\n"); + buffer.append (" partials: " + partial_count + "\n"); + if (exon_count == gene_count) { + buffer.append (" bases: " + + gene_bases_count + "\n"); + } else { + buffer.append (" bases (excluding introns): " + + gene_bases_count + "\n"); + buffer.append (" bases (including introns): " + + gene_bases_count_with_introns + "\n"); + buffer.append (" exons: " + exon_count + "\n"); + buffer.append (" average exon length: " + + 10L * gene_bases_count / exon_count / 10.0 + "\n"); + buffer.append (" average intron length: " + + 10L * (gene_bases_count_with_introns - + gene_bases_count) / + (exon_count - gene_count) / 10.0 + "\n"); + buffer.append (" average number of exons per gene: " + + 100L * exon_count / gene_count / 100.00 + "\n"); + } + buffer.append (" density: " + + 1000000L * gene_count / + entry_group.getSequenceLength () / 1000.0 + + " genes per kb (" + + entry_group.getSequenceLength () / gene_count + + " bases per gene)\n"); + buffer.append (" average length: " + + gene_bases_count / gene_count + "\n"); + buffer.append (" average length (including introns): " + + gene_bases_count_with_introns / gene_count + "\n"); + buffer.append (" coding percentage: " + + 1000L * gene_bases_count / + entry_group.getSequenceLength () / 10.0 + "\n"); + buffer.append (" coding percentage (including introns): " + + 1000L * gene_bases_count_with_introns / + entry_group.getSequenceLength () / 10.0 + "\n\n"); + + buffer.append (" gene sequence composition:\n\n"); + + final StringVector gene_base_summary = + SelectionViewer.getBaseSummary (gene_bases); + + for (int i = 0 ; i < gene_base_summary.size () ; ++i) { + buffer.append (" "); + buffer.append (gene_base_summary.elementAt (i)).append ("\n"); + } + + buffer.append ("\n"); + } + + if (pseudo_gene_count > 0) { + buffer.append ("Pseudo genes (CDS features with a /pseudo " + + "qualifier):\n"); + + buffer.append (" count: " + pseudo_gene_count + "\n"); + buffer.append (" bases: " + pseudo_gene_bases_count + "\n"); + buffer.append (" average length: " + + pseudo_gene_bases_count / pseudo_gene_count + "\n\n"); + } + + if (pseudo_gene_count > 0) { + buffer.append ("All CDS features:\n"); + + buffer.append (" count: " + cds_count + "\n"); + buffer.append (" bases: " + cds_bases_count + "\n"); + buffer.append (" average length: " + + cds_bases_count / cds_count + "\n\n"); + } + } + + final Strand strand; + + if (strand_flag == FORWARD || strand_flag == BOTH) { + strand = entry_group.getBases ().getForwardStrand (); + } else { + strand = entry_group.getBases ().getReverseStrand (); + } + + final StringVector base_summary = + SelectionViewer.getBaseSummary (strand.getStrandBases ()); + + buffer.append ("\nOverall sequence composition:\n\n"); + + for (int i = 0 ; i < base_summary.size () ; ++i) { + buffer.append (base_summary.elementAt (i)).append ("\n"); + } + + buffer.append ("\nSummary of the active entries:\n"); + + final Enumeration e = table.keys (); + + while (e.hasMoreElements()) { + final String this_key = (String) e.nextElement(); + final Hashtable colour_table = (Hashtable) table.get (this_key); + + buffer.append (this_key + ": "); + + final StringBuffer colour_string = new StringBuffer (); + + final Enumeration colour_enum = colour_table.keys (); + + int total = 0; + + while (colour_enum.hasMoreElements()) { + final String this_colour = (String) colour_enum.nextElement(); + + final int colour_count = + ((Integer) colour_table.get (this_colour)).intValue (); + + total += colour_count; + + final String end_string; + + if (this_colour.equals ("no colour")) { + end_string = "no colour"; + } else { + end_string = "colour: " + this_colour; + } + + if (colour_count == 1) { + colour_string.append (" one has " + end_string + "\n"); + } else { + colour_string.append (" " + colour_count + " have " + + end_string + "\n"); + } + } + + buffer.append (total + "\n"); + + buffer.append (colour_string); + } + + file_viewer.setText (buffer.toString ()); + } + + /** + * This is the EntryGroup object that we are viewing. + **/ + private EntryGroup entry_group; + + /** + * The strand indicator that was passed to the constructor. + **/ + private int strand_flag; + + /** + * The FileViewer object that is displaying the EntryGroup. + **/ + private FileViewer file_viewer; + + /** + * The Frame that was passed to the constructor. + **/ + private JFrame parent_frame; +} diff --git a/uk/ac/sanger/artemis/components/EntryGroupMenu.java b/uk/ac/sanger/artemis/components/EntryGroupMenu.java new file mode 100644 index 000000000..5e02cbbd5 --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryGroupMenu.java @@ -0,0 +1,336 @@ +/* EntryGroupMenu.java + * + * created: Fri Aug 27 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/EntryGroupMenu.java,v 1.1 2004-06-09 09:46:31 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A menu containing the current entries in an EntryGroup. + * + * @author Kim Rutherford + * @version $Id: EntryGroupMenu.java,v 1.1 2004-06-09 09:46:31 tjc Exp $ + **/ + +public class EntryGroupMenu extends JMenu + implements EntryGroupChangeListener, EntryChangeListener { + /** + * Create a new EntryGroupMenu object that will show the entries in the + * given EntryGroup. + * @param frame The JFrame that owns this JMenu. + * @param entry_group The EntryGroup object to display. + * @param menu_name The name of the new menu. + **/ + public EntryGroupMenu (final JFrame frame, + final EntryGroup entry_group, + final String menu_name) { + super (menu_name); + + this.frame = frame; + this.entry_group = entry_group; + + entry_group.addEntryGroupChangeListener (this); + entry_group.addEntryChangeListener (this); + + refreshMenu (); + } + + /** + * Create a new EntryGroupMenu object that will show the entries in the + * given EntryGroup. + * @param frame The JFrame that owns this JMenu. + * @param entry_group The EntryGroup object to display. + **/ + public EntryGroupMenu (final JFrame frame, + final EntryGroup entry_group) { + this (frame, entry_group, "Entries"); + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can update the display if entries + * are added or deleted. + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.ENTRY_ADDED: + case EntryGroupChangeEvent.ENTRY_DELETED: + case EntryGroupChangeEvent.ENTRY_INACTIVE: + case EntryGroupChangeEvent.ENTRY_ACTIVE: + case EntryGroupChangeEvent.NEW_DEFAULT_ENTRY: + refreshMenu (); + break; + } + } + + /** + * Implementation of the EntryChangeListener interface. + **/ + public void entryChanged (final EntryChangeEvent event) { + if (event.getType () == EntryChangeEvent.NAME_CHANGED) { + refreshMenu (); + } + } + + /** + * Update the menus to the reflect the current contents of the EntryGroup. + **/ + private void refreshMenu () { + removeAll (); + + if (entry_group == null || entry_group.size () == 0) { + add (new JMenuItem ("(No Entries Currently)")); + return; + } + + final JMenu set_entry_name_menu = new JMenu ("Set Name Of Entry"); + + add (set_entry_name_menu); + + final JMenu set_default_menu = new JMenu ("Set Default Entry"); + final JMenu delete_entry_menu = new JMenu ("Remove An Entry"); + + addSeparator (); + + add (set_default_menu); + add (delete_entry_menu); + + final JMenuItem delete_active_entries_menu = + new JMenuItem ("Remove Active Entries"); + + delete_active_entries_menu.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + deleteActiveEntries (); + } + }); + + add (delete_active_entries_menu); + + final JMenuItem deactivate_all = new JMenuItem ("Deactivate All Entries"); + + deactivate_all.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + for (int i = 0 ; i < entry_group.size () ; ++i) { + final Entry this_entry = entry_group.elementAt (i); + + entry_group.setIsActive (this_entry, false); + } + } + }); + + add (deactivate_all); + + entry_components = new Vector (); + + addSeparator (); + + for (int i = 0 ; i < entry_group.size () ; ++i) { + final Entry this_entry = entry_group.elementAt (i); + + String entry_name = this_entry.getName (); + + if (entry_name == null) { + entry_name = "no name"; + } + + final JMenuItem set_entry_name_item = new JMenuItem (entry_name); + set_entry_name_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + setEntryName (this_entry); + } + }); + + set_entry_name_menu.add (set_entry_name_item); + + final JMenuItem delete_entry_item = new JMenuItem (entry_name); + delete_entry_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (this_entry.hasUnsavedChanges ()) { + final String message; + + if (this_entry.getName () != null) { + message = "there are unsaved changes in " + + this_entry.getName () + " - really remove?"; + } else { + message = "there are unsaved changes in entry #" + + entry_group.indexOf (this_entry) + " - really remove?"; + } + + final YesNoDialog yes_no_dialog = + new YesNoDialog (frame, message); + + if (!yes_no_dialog.getResult ()) { + return; + } + } + entry_group.remove (this_entry); + } + }); + + delete_entry_menu.add (delete_entry_item); + + final JMenuItem set_default_entry_item = new JMenuItem (entry_name); + set_default_entry_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + entry_group.setDefaultEntry (this_entry); + } + }); + + set_default_menu.add (set_default_entry_item); + + add (entry_group.elementAt (i)); + } + } + + /** + * Add a Label or Checkbox for the given Entry to this component. + **/ + private void add (final Entry entry) { + String entry_name = entry.getName (); + + if (entry_name == null) { + entry_name = "no name"; + } + + if (entry_group.getDefaultEntry () == entry) { + entry_name = entry_name + " (default entry)"; + } + + final JCheckBoxMenuItem new_component = + new JCheckBoxMenuItem (entry_name, entry_group.isActive (entry)); + + new_component.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent event) { + final int button_index = + entry_components.indexOf (event.getSource ()); + + if (event.getStateChange () == ItemEvent.SELECTED) { + entry_group.setIsActive (button_index, true); + } else { + entry_group.setIsActive (button_index, false); + } + } + }); + + entry_components.addElement (new_component); + add (new_component); + } + + /** + * Create a new TextRequester component and set the name of the default + * Entry to whatever the user types. + **/ + private void setEntryName (final Entry entry) { + final TextRequester text_requester = + new TextRequester ("New name for the entry?", 18, ""); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + if (event.getType () == TextRequesterEvent.CANCEL) { + return; + } + + final String requester_text = event.getRequesterText ().trim (); + if (requester_text.length () > 0) { + if (entry.setName (requester_text)) { + // it worked + } else { + new MessageDialog (frame, + "could not set the name of the default entry"); + } + } + } + }); + + text_requester.show (); + } + + /** + * Delete the active entries after asking the user. + **/ + private void deleteActiveEntries () { + if (Options.getOptions ().isNoddyMode ()) { + final YesNoDialog dialog = + new YesNoDialog (frame, + "Are you sure you want to remove the " + + "active entries?"); + + if (!dialog.getResult ()) { + return; + } + } + + for (int i = entry_group.size () - 1 ; i >= 0 ; --i) { + final Entry this_entry = entry_group.elementAt (i); + + if (this_entry.hasUnsavedChanges ()) { + final String message; + + if (this_entry.getName () != null) { + message = "there are unsaved changes in " + + this_entry.getName () + " - really remove?"; + } else { + message = "there are unsaved changes in entry #" + + (i + 1) + " - really remove?"; + } + + final YesNoDialog yes_no_dialog = + new YesNoDialog (frame, message); + + if (!yes_no_dialog.getResult ()) { + continue; + } + } + + if (entry_group.isActive (this_entry)) { + entry_group.remove (this_entry); + } + } + } + + /** + * A vector containing the Entry objects that this EntryEdit object knows + * about. This reference is obtained from owning_component. + **/ + private EntryGroup entry_group; + + /** + * A vector containing one Checkbox or Label for each Entry in the + * EntryGroup object. + **/ + private Vector entry_components = new Vector (); + + /** + * The JFrame reference that was passed to the constructor. + **/ + private JFrame frame = null; +} + diff --git a/uk/ac/sanger/artemis/components/EntryGroupPanel.java b/uk/ac/sanger/artemis/components/EntryGroupPanel.java new file mode 100644 index 000000000..b846af6db --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryGroupPanel.java @@ -0,0 +1,376 @@ +/* EntryGroupPanel.java + * + * created: Wed Jun 21 2000 + * + * This file is part of Artemis + * + * Copyright(C) 2000,2001,2002 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/EntryGroupPanel.java,v 1.1 2004-06-09 09:46:32 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** + * A JPanel that can show an EntryGroup(in some way). + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: EntryGroupPanel.java,v 1.1 2004-06-09 09:46:32 tjc Exp $ + **/ + +abstract public class EntryGroupPanel extends CanvasPanel +{ + /** shortcut for View FASTA in browser. */ + final int fasta_in_browser_key = KeyEvent.VK_F; + + /** shortcut for View FASTA. */ + final int view_fasta_key = KeyEvent.VK_R; + + /** shortcut for View BLASTP in browser. */ + final int blastp_in_browser_key = KeyEvent.VK_B; + + /** shortcut for View BLASTP. */ + final int view_blastp_key = KeyEvent.VK_TAB; + + /** EntryGroup this component is displaying */ + private EntryGroup entry_group; + + /** + * This is a reference to the Selection object that was passed to the + * constructor. + **/ + private Selection selection; + + /** + * This is a reference to the GotoEventSource object that was passed to the + * constructor. + **/ + private GotoEventSource goto_event_source; + + /** + * The BasePlotGroup object that was passed to the constructor. + **/ + private BasePlotGroup base_plot_group; + + /** + * Create a new EntryGroupPanel for the given EntryGroup. + * @param entry_group The EntryGroup that this component will display. + * @param selection The Selection object for this component. Selected + * objects will be highlighted. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public EntryGroupPanel(final EntryGroup entry_group, + final Selection selection, + final GotoEventSource goto_event_source, + final BasePlotGroup base_plot_group) + { + this.entry_group = entry_group; + this.selection = selection; + this.goto_event_source = goto_event_source; + this.base_plot_group = base_plot_group; + + getCanvas().addKeyListener(new KeyAdapter() + { + public void keyPressed(final KeyEvent event) + { + handleKeyPress(event); + } + }); + } + + /** + * Return an object that implements the GotoEventSource interface and which + * is relative to the sequence that this DisplayComponent is displaying. + **/ + public GotoEventSource getGotoEventSource() + { + return goto_event_source; + } + + /** + * Walk up through parents of this JComponent and return the JFrame at the + * top. + **/ + public JFrame getParentFrame() + { + Component parent = this.getParent(); + + while(parent != null && !(parent instanceof JFrame)) + parent = parent.getParent(); + + return (JFrame)parent; + } + + /** + * Return the EntryGroup object that this FeatureDisplay is displaying. + **/ + public EntryGroup getEntryGroup() + { + return entry_group; + } + + /** + * Returns the Selection that was passed to the constructor. + **/ + public Selection getSelection() + { + return selection; + } + + /** + * Returns the BasePlotGroup that was passed to the constructor. + **/ + public BasePlotGroup getBasePlotGroup() + { + return base_plot_group; + } + + /** + * This method sends an GotoEvent to all the GotoEvent listeners that will + * make the start of the first selected Feature or FeatureSegment visible. + **/ + protected void makeSelectionVisible() + { + final Marker first_base = getSelection().getStartBaseOfSelection(); + + final GotoEvent new_event = new GotoEvent(this, first_base); + + getGotoEventSource().sendGotoEvent(new_event); + } + + /** + * Return true if and only if the given MouseEvent(a mouse press) should + * pop up a JPopupMenu. + **/ + protected boolean isMenuTrigger(final MouseEvent event) + { + if(event.isPopupTrigger() || + (event.getModifiers() & InputEvent.BUTTON3_MASK) != 0) + return true; + else + return false; + } + + /** + * Handle key press events. + **/ + private void handleKeyPress(final KeyEvent event) + { + // this is done so that menu shortcuts don't cause each action to be + // performed twice + if(!event.isShiftDown() && event.getModifiers() != 0) + return; + + final FeatureVector selected_features = getSelection().getAllFeatures(); + + if(selected_features.size() == 0) + { + final MarkerRange marker_range = getSelection().getMarkerRange(); + + if(marker_range != null) + { + switch(event.getKeyCode()) + { + case AddMenu.CREATE_FROM_BASE_RANGE_KEY_CODE: + AddMenu.createFeatureFromBaseRange(getParentFrame(), + getSelection(), + entry_group, + getGotoEventSource()); + break; + } + } + + } + else + { + final Feature first_selected_feature = selected_features.elementAt(0); + + final int feature_index = + getEntryGroup().indexOf(first_selected_feature); + + final int event_key_code = event.getKeyCode(); + + switch(event_key_code) + { + case KeyEvent.VK_UP: + case KeyEvent.VK_DOWN: + case KeyEvent.VK_LEFT: + case KeyEvent.VK_RIGHT: + { + if((event_key_code == KeyEvent.VK_LEFT || + event_key_code == KeyEvent.VK_RIGHT) && + !event.isShiftDown()) + break; + + final FeatureSegmentVector selected_segments = + getSelection().getSelectedSegments(); + + if(event.isShiftDown()) + { + if(selected_segments.size() > 0) + { + final FeatureSegment selected_segment = + selected_segments.elementAt(0); + + final Feature selected_segment_feature = + selected_segment.getFeature(); + + final FeatureSegmentVector feature_segments = + selected_segment_feature.getSegments(); + + final int segment_index = + feature_segments.indexOf(selected_segment); + + if(event_key_code == KeyEvent.VK_UP || + event_key_code == KeyEvent.VK_LEFT && + !selected_segment_feature.isForwardFeature() || + event_key_code == KeyEvent.VK_RIGHT && + selected_segment_feature.isForwardFeature()) + { + final int segment_count = feature_segments.size(); + final int new_index = segment_index + 1; + if(segment_index < segment_count - 1) + selection.set(feature_segments.elementAt(new_index)); + } + else + { + if(segment_index > 0) + { + final int new_index = segment_index - 1; + selection.set(feature_segments.elementAt(new_index)); + } + } + } + } + else + { + if(event_key_code == KeyEvent.VK_DOWN) + { + if(feature_index < + getEntryGroup().getAllFeaturesCount() - 1) + { + selection.set(getEntryGroup().featureAt(feature_index + 1)); + makeSelectionVisible(); + } + } + else + { + if(feature_index > 0) + { + selection.set(getEntryGroup().featureAt(feature_index - 1)); + makeSelectionVisible(); + } + } + } + } + break; + case EditMenu.EDIT_FEATURES_KEY_CODE: + EditMenu.editSelectedFeatures(getParentFrame(), + getEntryGroup(), + getSelection(), + getGotoEventSource()); + break; + case EditMenu.UNDO_KEY_CODE: + EditMenu.undo(getParentFrame(), getSelection(), getEntryGroup()); + break; + case EditMenu.MERGE_FEATURES_KEY_CODE: + EditMenu.mergeFeatures(getParentFrame(), getSelection(), + getEntryGroup()); + break; + case EditMenu.DUPLICATE_KEY_CODE: + EditMenu.duplicateFeatures(getParentFrame(), getSelection(), + getEntryGroup()); + break; + case EditMenu.DELETE_FEATURES_KEY_CODE: + EditMenu.deleteSelectedFeatures(getParentFrame(), getSelection(), + getEntryGroup()); + break; + case EditMenu.TRIM_FEATURES_KEY_CODE: + EditMenu.trimSelected(getParentFrame(), getSelection(), + getEntryGroup(), false, true); + break; + case EditMenu.TRIM_FEATURES_TO_NEXT_ANY_KEY_CODE: + EditMenu.trimSelected(getParentFrame(), getSelection(), + getEntryGroup(), true, true); + break; + case EditMenu.EXTEND_TO_PREVIOUS_STOP_CODON_KEY_CODE: + EditMenu.extendToORF(getParentFrame(), getSelection(), + getEntryGroup(), false); + break; + case AddMenu.CREATE_FROM_BASE_RANGE_KEY_CODE: + AddMenu.createFeatureFromBaseRange(getParentFrame(), getSelection(), + entry_group, + getGotoEventSource()); + break; + case ViewMenu.VIEW_FEATURES_KEY_CODE: + ViewMenu.viewSelectedFeatures(getParentFrame(), getSelection()); + break; + case ViewMenu.PLOT_FEATURES_KEY_CODE: + ViewMenu.plotSelectedFeatures(getParentFrame(), getSelection()); + break; + case ViewMenu.OVERVIEW_KEY_CODE: + new EntryGroupInfoDisplay(getParentFrame(), entry_group); + break; + case ViewMenu.FASTA_IN_BROWSER_KEY_CODE: + viewResults("fasta", true); + break; + case ViewMenu.BLASTP_IN_BROWSER_KEY_CODE: + viewResults("blastp", true); + break; + case ViewMenu.VIEW_FASTA_KEY_CODE: + viewResults("fasta", false); + break; + case ViewMenu.VIEW_BLASTP_KEY_CODE: + viewResults("blastp", false); + break; + case ViewMenu.VIEW_HTH_KEY_CODE: + viewResults("hth", false); + break; + } + } + } + + /** + * Show the output file from an external program (like fasta) for the + * selected Feature objects. The name of the file to read is stored in a + * feature qualifier. The qualifier used is the program name plus "_file". + * @param send_to_browser if true the results should be sent straight to + * the web browser rather than using a SearchResultViewer object. + **/ + private void viewResults(final String program_name, + final boolean send_to_browser) + { + final boolean sanger_options = + Options.getOptions().getPropertyTruthValue("sanger_options"); + + if(sanger_options && send_to_browser) + ViewMenu.viewExternalResults(getParentFrame(), getSelection(), + program_name, true); + else + ViewMenu.viewExternalResults(getParentFrame(), getSelection(), + program_name, false); + } + +} diff --git a/uk/ac/sanger/artemis/components/EntryHeaderEdit.java b/uk/ac/sanger/artemis/components/EntryHeaderEdit.java new file mode 100644 index 000000000..007460e96 --- /dev/null +++ b/uk/ac/sanger/artemis/components/EntryHeaderEdit.java @@ -0,0 +1,209 @@ +/* EntryHeaderEdit.java + * + * created: Wed Jun 16 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/EntryHeaderEdit.java,v 1.1 2004-06-09 09:46:33 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.*; + +import java.io.*; + +/** + * Objects of this class are used to edit the header of an Entry. + * + * @author Kim Rutherford + * @version $Id: EntryHeaderEdit.java,v 1.1 2004-06-09 09:46:33 tjc Exp $ + **/ + +public class EntryHeaderEdit + implements EntryChangeListener, EntryGroupChangeListener, + DocumentListener { + /** + * Create a new EntryHeaderEdit object for the given Entry. + **/ + public EntryHeaderEdit (final EntryGroup entry_group, + final Entry edit_entry) { + + this.edit_entry = edit_entry; + this.entry_group = entry_group; + + file_viewer = new FileViewer ("Artemis Entry Header Editor: " + + edit_entry.getName () == null ? + "" : edit_entry.getName ()); + + file_viewer.getContentPane ().add (error_text, "North"); + + readHeader (); + + file_viewer.getTextArea ().setEditable (true); + file_viewer.getTextArea ().getDocument ().addDocumentListener (this); + + getEntry ().addEntryChangeListener (this); + entry_group.addEntryGroupChangeListener (this); + + file_viewer.addWindowListener (new WindowAdapter () { + public void windowClosed (WindowEvent event) { + stopListening (); + } + }); + } + + + /** + * Remove this object as a feature and entry change listener. + **/ + public void stopListening () { + getEntry ().removeEntryChangeListener (this); + entry_group.removeEntryGroupChangeListener (this); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so we can update this component if the header changes + * for another reason. + **/ + public void entryChanged (EntryChangeEvent event) { + switch (event.getType ()) { + case EntryChangeEvent.HEADER_CHANGED: + + if (event.getSource () == current_text) { + // don't bother with events from us + return; + } + + // re-read the information from the entry + readHeader (); + break; + default: + // do nothing + break; + } + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so we can notify the user if of this component + * if the entry gets deleted. + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.ENTRY_DELETED: + if (event.getEntry () == edit_entry) { + stopListening (); + file_viewer.dispose (); + } + break; + default: + // do nothing + break; + } + } + + public void changedUpdate(DocumentEvent e) { + textValueChanged (); + } + public void insertUpdate(DocumentEvent e) { + textValueChanged (); + } + public void removeUpdate(DocumentEvent e) { + textValueChanged (); + } + + /** + * Implementation of the TextListener interface. When the text changes we + * update the Feature object that we are showing. + **/ + public void textValueChanged () { + if (current_text.equals (file_viewer.getText ())) { + // the text hasn't really changed + return; + } + + current_text = file_viewer.getText (); + + try { + // ignore text change events while reading + edit_entry.setHeaderText (current_text); + error_text.setText (""); + } catch (uk.ac.sanger.artemis.io.ReadFormatException e) { + error_text.setText (e + (e.getLineNumber () > 1 ? + " at line: " + e.getLineNumber () : + "")); + } catch (java.io.IOException e) { + error_text.setText (e.toString ()); + } + } + + /** + * Read the header of edit_entry into this component. + **/ + public void readHeader () { + final String header = edit_entry.getHeaderText (); + + if (header != null) { + file_viewer.setText (header); + + current_text = file_viewer.getText (); + } + } + + /** + * Return the Entry that this object is displaying. + **/ + private Entry getEntry () { + return edit_entry; + } + + /** + * The EntryGroup that passed to the constructor. + **/ + private EntryGroup entry_group; + + /** + * The Entry that contains the Feature this object is displaying. + **/ + private Entry edit_entry; + + /** + * The FileViewer object that is displaying the feature. + **/ + private FileViewer file_viewer; + + /** + * The text version of the Feature we are currently displaying. + **/ + private String current_text = ""; + + /** + * A Label for showing errors and messages. + **/ + private JLabel error_text = new JLabel (""); +} + diff --git a/uk/ac/sanger/artemis/components/ExternalProgramOptions.java b/uk/ac/sanger/artemis/components/ExternalProgramOptions.java new file mode 100644 index 000000000..01714cbb0 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ExternalProgramOptions.java @@ -0,0 +1,66 @@ +/* ExternalProgramOptions.java + * + * created: Mon Oct 4 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/ExternalProgramOptions.java,v 1.1 2004-06-09 09:46:34 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.awt.*; + +/** + * This component allows the user to set the options of an ExternalProgram. + * + * @author Kim Rutherford + * @version $Id: ExternalProgramOptions.java,v 1.1 2004-06-09 09:46:34 tjc Exp $ + **/ + +public class ExternalProgramOptions { + /** + * Create a new ExternalProgramOptions object for the given + * ExternalProgram. + **/ + public ExternalProgramOptions (final ExternalProgram external_program) { + if (external_program.getType () == ExternalProgram.AA_PROGRAM || + external_program.getType () == ExternalProgram.DNA_PROGRAM) { + + final TextRequester requester = + new TextRequester ("Options for " + external_program.getName () + ":", + 18, external_program.getProgramOptions ()); + + requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + final String requester_text = event.getRequesterText ().trim (); + if (requester_text.length () > 0) { + external_program.setProgramOptions (requester_text); + } + } + }); + + requester.show (); + } else { + throw new Error ("internal error - please hit the programmer"); + } + } +} diff --git a/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java b/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java new file mode 100644 index 000000000..17f6a6586 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java @@ -0,0 +1,172 @@ +/* FeatureAminoAcidViewer.java + * + * created: Sat Dec 19 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java,v 1.1 2004-06-09 09:46:35 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import java.awt.event.*; + +/** + * A component for viewing the amino acids of a protein feature. Once + * created this component listens for FeatureChange events to keep the the + * sequence up to date. + * + * @author Kim Rutherford + * @version $Id: FeatureAminoAcidViewer.java,v 1.1 2004-06-09 09:46:35 tjc Exp $ + **/ + +public class FeatureAminoAcidViewer + implements EntryChangeListener, FeatureChangeListener { + /** + * Create a new FeatureAminoAcidViewer component to display the amino acids + * of the given Feature. + * @param feature The feature to view. + * @param include_numbers If true then the amino acids will be numbered + * (every second line of the display will be numbers rather than + * sequence). + **/ + public FeatureAminoAcidViewer (final Feature feature, + final boolean include_numbers) { + this.feature = feature; + this.entry = feature.getEntry (); + this.include_numbers = include_numbers; + + sequence_viewer = + new SequenceViewer ("Feature base viewer for feature:" + + getFeature ().getIDString (), include_numbers); + + redisplay (); + + getFeature ().getEntry ().addEntryChangeListener (this); + getFeature ().addFeatureChangeListener (this); + + sequence_viewer.addWindowListener (new WindowAdapter () { + public void windowClosed (WindowEvent event) { + stopListening (); + } + }); + } + + /** + * Remove this object as a entry and feature change listener. + **/ + private void stopListening () { + getEntry ().removeEntryChangeListener (this); + getFeature ().removeFeatureChangeListener (this); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so we can delete this component if the feature gets + * deleted. + **/ + public void entryChanged (EntryChangeEvent event) { + switch (event.getType ()) { + case EntryChangeEvent.FEATURE_DELETED: + if (event.getFeature () == getFeature ()) { + stopListening (); + sequence_viewer.dispose (); + } + break; + default: + // do nothing; + break; + } + } + /** + * Implementation of the FeatureChangeListener interface. We need to + * listen to feature change events from the Features in this object so that + * we can keep the display up to date. + * @param event The change event. + **/ + public void featureChanged (FeatureChangeEvent event) { +// System.out.println ("FeatureViewer: feature change event type: " + +// event.getType ()); + + // re-read the information from the feature + redisplay (); + } + + /** + * Redisplay the amino acids. + **/ + private void redisplay () { + String small_note = getFeature ().getNote (); + + if (small_note == null) { + small_note = getFeature ().getIDString (); + } else { + if (small_note.length () > 50) { + small_note = small_note.substring (0, 50); + } + } + + final String comment = ">" + small_note + " - " + + getFeature ().getFirstBase () + ": " + + getFeature ().getLastBase () + " MW: " + + getFeature ().getMolecularWeight (); + + final String sequence = + getFeature ().getTranslation ().toString ().toUpperCase (); + + sequence_viewer.setSequence (comment, sequence); + } + + + /** + * Return the feature this component is showing information about. + **/ + private Feature getFeature () { + return feature; + } + + /** + * Return the Entry that contains the Feature this object is displaying. + **/ + private Entry getEntry () { + return entry; + } + + /** + * The Feature that this component is showing information about. + **/ + private Feature feature = null; + + /** + * The SequenceViewer object that is displaying the feature bases. + **/ + private SequenceViewer sequence_viewer; + + /** + * The Entry that contains the Feature this object is displaying. + **/ + private Entry entry; + + /** + * If true then the amino acids will be numbered (every second line of the + * display will be numbers rather than sequence). + **/ + private final boolean include_numbers; +} diff --git a/uk/ac/sanger/artemis/components/FeatureBaseViewer.java b/uk/ac/sanger/artemis/components/FeatureBaseViewer.java new file mode 100644 index 000000000..929377d56 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureBaseViewer.java @@ -0,0 +1,166 @@ +/* FeatureBaseViewer.java + * + * created: Sat Dec 19 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureBaseViewer.java,v 1.1 2004-06-09 09:46:36 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import java.awt.event.*; + +/** + * A component for viewing the bases of a feature. Once created this + * component listens for FeatureChange events to keep the the sequence up to + * date. + * + * @author Kim Rutherford + * @version $Id: FeatureBaseViewer.java,v 1.1 2004-06-09 09:46:36 tjc Exp $ + **/ + +public class FeatureBaseViewer + implements EntryChangeListener, FeatureChangeListener { + /** + * Create a new FeatureBaseViewer component to display the bases of the + * given Feature. + * @param feature The feature to view. + * @param include_numbers If true then the sequence will be numbered + * (every second line of the display will be numbers rather than + * sequence). + **/ + public FeatureBaseViewer (final Feature feature, + final boolean include_numbers) { + this.feature = feature; + this.entry = feature.getEntry (); + this.include_numbers = include_numbers; + + sequence_viewer = + new SequenceViewer ("Feature base viewer for feature:" + + getFeature ().getIDString (), include_numbers); + + redisplay (); + + getFeature ().getEntry ().addEntryChangeListener (this); + getFeature ().addFeatureChangeListener (this); + + sequence_viewer.addWindowListener (new WindowAdapter () { + public void windowClosed (WindowEvent event) { + stopListening (); + } + }); + } + + /** + * Remove this object as a entry and feature change listener. + **/ + private void stopListening () { + getEntry ().removeEntryChangeListener (this); + getFeature ().removeFeatureChangeListener (this); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so we can delete this component if the feature gets + * deleted. + **/ + public void entryChanged (EntryChangeEvent event) { + switch (event.getType ()) { + case EntryChangeEvent.FEATURE_DELETED: + if (event.getFeature () == getFeature ()) { + stopListening (); + sequence_viewer.dispose (); + } + break; + default: + // do nothing; + break; + } + } + + /** + * Implementation of the FeatureChangeListener interface. We need to + * listen to feature change events from the Features in this object so that + * we can keep the display up to date. + * @param event The change event. + **/ + public void featureChanged (FeatureChangeEvent event) { + // re-read the information from the feature + redisplay (); + } + + /** + * Redisplay the bases. + **/ + private void redisplay () { + String small_note = getFeature ().getNote (); + + if (small_note == null) { + small_note = ""; + } else { + if (small_note.length () > 50) { + small_note = small_note.substring (0, 50); + } + } + + final String comment = ">" + small_note + " - " + + getFeature ().getFirstBase () + ": " + + getFeature ().getLastBase (); + + final String bases = getFeature ().getBases ().toUpperCase (); + sequence_viewer.setSequence (comment, bases); + } + + /** + * Return the feature this component is showing information about. + **/ + private Feature getFeature () { + return feature; + } + + /** + * Return the Entry that contains the Feature this object is displaying. + **/ + private Entry getEntry () { + return entry; + } + + /** + * The Feature that this component is showing information about. + **/ + private Feature feature = null; + + /** + * The SequenceViewer object that is displaying the feature bases. + **/ + private SequenceViewer sequence_viewer; + + /** + * The Entry that contains the Feature this object is displaying. + **/ + private Entry entry; + + /** + * If true then the amino acids will be numbered (every second line of the + * display will be numbers rather than sequence). + **/ + private final boolean include_numbers; +} diff --git a/uk/ac/sanger/artemis/components/FeatureDisplay.java b/uk/ac/sanger/artemis/components/FeatureDisplay.java new file mode 100644 index 000000000..db5a79546 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureDisplay.java @@ -0,0 +1,4699 @@ +/* FeatureDisplay.java + * + * created: Fri Oct 9 1998 + * + * This file is part of Artemis + * + * Copyright(C) 1998,1999,2000,2001,2002 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/FeatureDisplay.java,v 1.1 2004-06-09 09:46:37 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.io.Range; +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import java.awt.event.*; +import java.awt.*; +import java.lang.Math; +import java.util.Vector; +import java.util.Comparator; + +import javax.swing.JScrollBar; +import javax.swing.JComponent; + +/** + * This component is used for displaying an Entry. + * + * @author Kim Rutherford + * @version $Id: FeatureDisplay.java,v 1.1 2004-06-09 09:46:37 tjc Exp $ + **/ + +public class FeatureDisplay extends EntryGroupPanel + implements EntryGroupChangeListener, + EntryChangeListener, FeatureChangeListener, + SelectionChangeListener, GotoListener, SequenceChangeListener, + DisplayComponent, OptionChangeListener, DisplayAdjustmentListener +{ + + /** Key code for calling zoomToSelection(). */ + final static public int ZOOM_TO_SELECTION_KEY = KeyEvent.VK_Z; + + final static public int SCROLLBAR_AT_TOP = 1; + final static public int SCROLLBAR_AT_BOTTOM = 2; + final static public int NO_SCROLLBAR = 3; + + public final static int FORWARD = Bases.FORWARD; + public final static int REVERSE = Bases.REVERSE; + + public final static int NO_FRAME = FeatureSegment.NO_FRAME; + public final static int FORWARD_STRAND = FeatureSegment.FORWARD_STRAND; + public final static int REVERSE_STRAND = FeatureSegment.REVERSE_STRAND; + public final static int FORWARD_FRAME_1 = FeatureSegment.FORWARD_FRAME_1; + public final static int FORWARD_FRAME_2 = FeatureSegment.FORWARD_FRAME_2; + public final static int FORWARD_FRAME_3 = FeatureSegment.FORWARD_FRAME_3; + public final static int REVERSE_FRAME_3 = FeatureSegment.REVERSE_FRAME_3; + public final static int REVERSE_FRAME_2 = FeatureSegment.REVERSE_FRAME_2; + public final static int REVERSE_FRAME_1 = FeatureSegment.REVERSE_FRAME_1; + public final static int SCALE_LINE = FeatureSegment.SCALE_LINE; + + + /** + * The JScrollBar for this FeatureDisplay object. We create the scrollbar + * as part of this object rather than in the EntryEdit component because we + * may need to change the parameters of the scrollbar later. + **/ + private JScrollBar scrollbar = null; + + /** A scroll bar for changing the viewing scale. */ + private JScrollBar scale_changer = null; + + /** Used to colour the frames. */ + private Color light_grey = new Color(240, 240, 240); + + /** Used to colour sequence line. */ + private Color not_so_light_grey = new Color(200, 200, 200); + + /** + * The colour used for the active entry line when + * one_line_per_entry is set. + **/ + private Color active_entry_colour = new Color(255, 255, 140); + + /** + * This Vector containing the references of those features that are + * currently visible. + **/ + private FeatureVector visible_features = new FeatureVector(); + + /** + * If true updateVisibleFeatureVector() will be called by paint(). + * updateVisibleFeatureVector() sets this to false, + * needVisibleFeatureVectorUpdate() sets this to true. + **/ + private boolean update_visible_features = true; + + /** Contains those objects listening for adjustment events. */ + final private Vector adjustment_listener_list = new Vector(); + + /** + * The index of the first base that we are displaying. + * Can be negative if hard_left_edge is true. + **/ + private int left_edge_base = 1; + + /** See getScaleFactor(). */ + private int scale_factor = 3; + + /** See getScaleFactor(). */ + private float scale_value = 1; + + /** true if labels should be shown. */ + private boolean show_labels = true; + + /** + * This variable is true if the forward frame lines(or forward entry lines + * - see one_line_per_entry) should be drawn. + **/ + private boolean show_forward_lines = true; + + /** + * This variable is true if the reverse frame lines(or reverse entry lines + * - see one_line_per_entry) should be drawn. + **/ + private boolean show_reverse_lines = true; + + /** + * If true draw all features, sequence and scale lines reverse complemented. + **/ + private boolean rev_comp_display = false; + + /** + * This variable is true if(for each strand) each entry should be on a + * separate line. + **/ + private boolean one_line_per_entry = false; + + /** + * If true the there will never be a gap between the left edge of the + * screen and the first visible base. + **/ + private boolean hard_left_edge = true; + + /** true if source features should be shown. */ + private boolean show_source_features = false; + + /** true if stop codons should be shown. */ + private boolean show_stop_codons = true; + + /** true if start codons should be shown. */ + private boolean show_start_codons = false; + + /** true if directional arrows should be shown on features. */ + private boolean show_feature_arrows; + + /** true a black border will be drawn around each feature. */ + private boolean show_feature_borders; + + /** + * This variable is true if each base should be drawn in a different colour + * at scale feature 1. + **/ + private boolean show_base_colours = false; + + /** + * All features(not just CDS features) will be drawn on the frame lines if + * and only if this variable is true. See setFrameFeaturesFlag() and + * getFrameFeaturesFlag(). + **/ + private boolean frame_features_flag = false; + + /** + * The position(s) of the last mouse click on the dna line. The + * MarkerRange contains a reference to the appropriate Strand and contains + * the base positions. See getMarkerRangeFromPosition() to understand why + * one click can give multiple bases. + **/ + 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; + + /** + * Features with a /score qualifier less than this value will not be shown. + **/ + private int current_min_score = 0; + + /** + * Features with a /score qualifier greater than this value will not be + * shown. + **/ + private int current_max_score = 100; + + private MouseEvent last_mouse_press_event; + + /** + * If set no DisplayAdjustment events will be sent. This is set by + * displayAdjustmentValueChanged() to prevent an event we send from + * returning to us(a FeatureDisplay can listen for DisplayAdjustment + * events from another FeatureDisplay). + **/ + private boolean disable_display_events = false; + + /** + * Set to true by selectionChanged() to tell updateVisibleFeatureVector() + * to raise the contents of the select before updating. + **/ + private boolean raise_selection_flag = false; + + /** the minimum distance in pixels between the labels. */ + private final static int MINIMUM_LABEL_SPACING = 80; + + /** colour used for A. */ + private static Color dark_green = new Color(0, 150, 0); + + /** + * Used by drawOneLetter() - declared here so that we we don't need to + * allocated a whole array each time drawOneLetter() is called. + **/ + private final char [] draw_one_char_temp_array = new char [1]; + + + /** + * Create a new FeatureDisplay object with the horizontal scrollbar at the + * bottom of the component. + * @param entry_group The EntryGroup that this component will display. + * @param selection The Selection object for this component. Selected + * objects will be highlighted. + * @param goto_event_source The object to use when we need to call + * gotoBase(). + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public FeatureDisplay(final EntryGroup entry_group, + final Selection selection, + final GotoEventSource goto_event_source, + final BasePlotGroup base_plot_group) + { + this(entry_group, selection, goto_event_source, + base_plot_group, SCROLLBAR_AT_BOTTOM); + } + + /** + * Create a new FeatureDisplay object. + * @param entry_group The EntryGroup that this component will display. + * @param owning_component The EntryEdit object that contains the selection + * that this component uses. + * @param scrollbar_at_top If true the horizontal scrollbar will be at the + * top of component. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + * @param scrollbar_style Controls the type of horizontal scrollbar. Must + * be one of SCROLLBAR_AT_TOP, SCROLLBAR_AT_BOTTOM or NO_SCROLLBAR. + **/ + public FeatureDisplay(final EntryGroup entry_group, + final Selection selection, + final GotoEventSource goto_event_source, + final BasePlotGroup base_plot_group, + final int scrollbar_style) + { + super(entry_group, selection, goto_event_source, base_plot_group); + + show_feature_arrows = + Options.getOptions().getPropertyTruthValue("draw_feature_arrows"); + + show_feature_borders = + Options.getOptions().getPropertyTruthValue("draw_feature_borders"); + + frame_features_flag = + Options.getOptions().getPropertyTruthValue("features_on_frame_lines"); + + one_line_per_entry = + Options.getOptions().getPropertyTruthValue("one_line_per_entry"); + + show_labels = + Options.getOptions().getPropertyTruthValue("feature_labels"); + +// getCanvas().addComponentListener(new ComponentAdapter() + addComponentListener(new ComponentAdapter() + { + public void componentResized(ComponentEvent e) + { + // update the scroll bar as soon as we know the size of the canvas + fixScrollbar(); + needVisibleFeatureVectorUpdate(); + fireAdjustmentEvent(DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT); + } + + public void componentShown(ComponentEvent e) + { + // update the scroll bar as soon as we know the size of the canvas + fixScrollbar(); + needVisibleFeatureVectorUpdate(); + fireAdjustmentEvent(DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT); + } + }); + + setScaleValue(); + + if(scrollbar_style == SCROLLBAR_AT_TOP) + createScrollbar(true); + else + { + if(scrollbar_style == SCROLLBAR_AT_BOTTOM) + createScrollbar(false); + } + + createScaleScrollbar(); + fixCanvasSize(); + fixScrollbar(); + addListeners(); + + needVisibleFeatureVectorUpdate(); + + fireAdjustmentEvent(DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT); + + getSelection().addSelectionChangeListener(this); + getGotoEventSource().addGotoListener(this); + + getEntryGroup().addEntryGroupChangeListener(this); + getEntryGroup().addEntryChangeListener(this); + getEntryGroup().addFeatureChangeListener(this); + + getBases().addSequenceChangeListener(this, Bases.MIN_PRIORITY); + + Options.getOptions().addOptionChangeListener(this); + } + + /** + * Returns the value of a flag that indicates whether this component can be + * traversed using Tab or Shift-Tab keyboard focus traversal - returns true + * for FeatureDisplay components + **/ +// tjc - deprecated replaced by isFocusable(). +//public boolean isFocusTraversable() +//{ +// return true; +//} + + /** + * Overriden to call fixCanvasSize() + **/ + public void setVisible(final boolean visible) + { + super.setVisible(visible); + fixCanvasSize(); + } + + /** + * Set value of the show label flag. + * @param show_label Show labels if and only if this argument is true. + **/ + public void setShowLabels(boolean show_labels) + { + if(this.show_labels != show_labels) + { + this.show_labels = show_labels; + fixCanvasSize(); + } + } + + /** + * Get the value of the "show label" flag. + **/ + public boolean getShowLabels() + { + return show_labels; + } + + /** + * Set value of the "show forward frame lines" flag. + * @param show_forward_lines Show forward frame lines if and only if + * this argument is true. + **/ + public void setShowForwardFrameLines(boolean show_forward_lines) + { + if(this.show_forward_lines != show_forward_lines) + { + this.show_forward_lines = show_forward_lines; + fixCanvasSize(); + } + } + + /** + * Get the value of the "show forward frame lines" flag. + **/ + public boolean getShowForwardFrameLines() + { + return show_forward_lines; + } + + /** + * Set value of the "show reverse frame lines" flag. + * @param show_reverse_lines Show frame lines if and only if this + * argument is true. + **/ + public void setShowReverseFrameLines(boolean show_reverse_lines) + { + if(this.show_reverse_lines != show_reverse_lines) + { + this.show_reverse_lines = show_reverse_lines; + fixCanvasSize(); + } + } + + /** + * Get the value of the "show source features" flag. + **/ + public boolean getShowSourceFeatures() + { + return show_source_features; + } + + /** + * Set value of the "show source features" flag. + * @param show_source_features Show features with a "source" key if and + * only if this argument is true. + **/ + public void setShowSourceFeatures(boolean show_source_features) + { + if(this.show_source_features != show_source_features) + { + this.show_source_features = show_source_features; + needVisibleFeatureVectorUpdate(); +// getCanvas().repaint(); + repaint(); + } + } + + /** + * Get the value of the "show frame lines" flag. + **/ + public boolean getShowReverseFrameLines() + { + return show_reverse_lines; + } + + /** + * Set value of the show base colours flag. + * @param show_base_colours At scale_factor less than two show each base in + * a different colour if and only if this argument is true. + **/ + public void setShowBaseColours(boolean show_base_colours) + { + if(this.show_base_colours != show_base_colours) + { + this.show_base_colours = show_base_colours; + if(getScaleFactor() > 1) + setScaleFactor(1); +// getCanvas().repaint(); + repaint(); + } + } + + /** + * Get the value of the "show base colours" flag. + **/ + public boolean getShowBaseColours() + { + return show_base_colours; + } + + /** + * Set value of the "one line per entry" flag. + * @param one_line_per_entry If true then each entry will be shown on a + * different line, instead of showing frame lines. + **/ + public void setOneLinePerEntry(final boolean one_line_per_entry) + { + if(this.one_line_per_entry != one_line_per_entry) + { + this.one_line_per_entry = one_line_per_entry; + fixCanvasSize(); + } + } + + /** + * Get the value of the "one line per entry" flag. + **/ + public boolean getOneLinePerEntryFlag() + { + return one_line_per_entry; + } + + /** + * Set value of the "hard left edge" flag. + * @param hard_left_edge If true the there will never be a gap between the + * left edge of the screen and the first visible base. If false base one + * can be moved to the centre of the display. + **/ + public void setHardLeftEdge(final boolean hard_left_edge) + { + if(this.hard_left_edge != hard_left_edge) + { + this.hard_left_edge = hard_left_edge; + if(hard_left_edge && getForwardBaseAtLeftEdge() < 1) + setFirstVisibleForwardBase(1); + + fixScrollbar(); + } + } + + /** + * Get the value of the "hard left edge" flag. + **/ + public boolean getHardLeftEdgeFlag() + { + return hard_left_edge; + } + + /** + * Set value of the show stop codons flag. + * @param show_stop_codons Show stop codons if and only if this argument is + * true. + **/ + public void setShowStopCodons(boolean show_stop_codons) + { + if(this.show_stop_codons != show_stop_codons) + { + this.show_stop_codons = show_stop_codons; + getCanvas().repaint(); + } + } + + /** + * Return the value of the "show stop codons" flag. + **/ + public boolean getShowStopCodons() + { + return show_stop_codons; + } + + /** + * Set value of the show start codons flag. + * @param show_start_codons Show start codons if and only if this argument + * is true. + **/ + public void setShowStartCodons(boolean show_start_codons) + { + if(this.show_start_codons != show_start_codons) + { + this.show_start_codons = show_start_codons; + getCanvas().repaint(); + } + } + + /** + * Return the value of the "show start codons" flag. + **/ + public boolean getShowStartCodons() + { + return show_start_codons; + } + + /** + * Set value of the reverse complement display flag. + * @param show_start_codons Draw all features and sequence reverse + * complemented if and only if this argument is true. + **/ + public void setRevCompDisplay(boolean rev_comp_display) + { + if(this.rev_comp_display != rev_comp_display) + { + this.rev_comp_display = rev_comp_display; + int remember_position = getCentreForwardBase(); + + // we want to keep the selection visible after the flip, so + // that will override the centre position + final Marker first_base_marker = + getSelection().getStartBaseOfSelection(); + + if(first_base_marker != null && baseVisible(first_base_marker)) + remember_position = first_base_marker.getRawPosition(); + + final Marker last_base_marker = + getSelection().getStartBaseOfSelection(); + + if(last_base_marker != null && baseVisible(last_base_marker)) + remember_position = last_base_marker.getRawPosition(); + + fireAdjustmentEvent(DisplayAdjustmentEvent.REV_COMP_EVENT); + + makeBaseVisibleInternal(remember_position, isRevCompDisplay(), true); + + needVisibleFeatureVectorUpdate(); + fixScrollbar(); + getCanvas().repaint(); + } + } + + /** + * Return the value of the "reverse complement display" flag. + **/ + public boolean isRevCompDisplay() + { + return rev_comp_display; + } + + /** + * Set value of the show feature arrows flag. + * @param show_feature_arrows Show directional arrows if and only if this + * argument is true. + **/ + public void setShowFeatureArrows(boolean show_feature_arrows) + { + if(this.show_feature_arrows != show_feature_arrows) + { + this.show_feature_arrows = show_feature_arrows; + getCanvas().repaint(); + } + } + + /** + * Return the value of the "show feature arrows" flag. + **/ + public boolean getShowFeatureArrows() + { + return show_feature_arrows; + } + + /** + * Set value of the show feature borders flag. + * @param show_feature_borders Draw a border around each feature if and + * only if this argument is true. + **/ + public void setShowFeatureBorders(boolean show_feature_borders) + { + if(this.show_feature_borders != show_feature_borders) + { + this.show_feature_borders = show_feature_borders; + getCanvas().repaint(); + } + } + + /** + * Return the value of the "show feature borders" flag. + **/ + public boolean getShowFeatureBorders() + { + return show_feature_borders; + } + + /** + * Set value of the show frame features flag. + * @param frame_features_flag All features(not just CDS features) will be + * drawn on the frame lines if and only if this argument is true. + **/ + public void setFrameFeaturesFlag(boolean frame_features_flag) + { + if(this.frame_features_flag != frame_features_flag) + { + this.frame_features_flag = frame_features_flag; + getCanvas().repaint(); + } + } + + /** + * Return the value of the "show frame features" flag. + **/ + public boolean getFrameFeaturesFlag() + { + return frame_features_flag; + } + + /** + * Set the value of the minimum score for this FeatureDisplay - features + * that have a /score lower than this value are never shown. + **/ + public void setMinimumScore(final int minimum_score) + { + current_min_score = minimum_score; + needVisibleFeatureVectorUpdate(); + getCanvas().repaint(); + } + + /** + * Return the value of the minimum score for this FeatureDisplay - see + * setMinimumScore(). + **/ + public int getMinimumScore() + { + return current_min_score; + } + + /** + * Set the value of the maximum score for this FeatureDisplay - features + * that have a /score higher than this value are never shown. + **/ + public void setMaximumScore(final int maximum_score) + { + current_max_score = maximum_score; + needVisibleFeatureVectorUpdate(); + getCanvas().repaint(); + } + + /** + * Return the value of the maximum score for this FeatureDisplay - see + * setMaximumScore(). + **/ + public int getMaximumScore() + { + return current_max_score; + } + + /** + * Redraw this component. This method is public so that other classes can + * force an update. for example this is called when the options files is + * re-read. + **/ + public void redisplay() + { + getCanvas().repaint(); + } + + /** + * Adds the specified event adjustment listener to receive adjustment + * change events from this object. + * @param l the event change listener. + **/ + public void addDisplayAdjustmentListener(DisplayAdjustmentListener l) + { + adjustment_listener_list.addElement(l); + } + + /** + * Removes the specified event listener so that it no longer receives + * adjustment change events from this object. + * @param l the event change listener. + **/ + public void removeDisplayAdjustmentListener(DisplayAdjustmentListener l) + { + adjustment_listener_list.removeElement(l); + } + + /** + * Handle key press events. This is static because making it non-static + * triggered a java.lang.VerifyError + **/ + private static void handleKeyPress(final FeatureDisplay feature_display, + final KeyEvent event) + { + // this is done so that menu shortcuts don't cause each action to be + // performed twice + if(event.getModifiers() != 0) + return; + + switch(event.getKeyCode()) + { + case ZOOM_TO_SELECTION_KEY: + FeaturePopup.zoomToSelection(feature_display); + break; + default: + break; + } + } + + /** + * Set the scale factor and update the display if the scale factor has + * changed. A factor of zero means the full translation will be visible. + * At higher scale factors only stop codons are visible, and a bigger + * number will mean more bases are visible. + **/ + public void setScaleFactor(int scale_factor) + { + if(this.scale_factor != scale_factor) + { + // we will try to keep the base in the centre of the view to stay where + // it is, so we save it's position in remember_position. + int remember_position = getCentreForwardBase(); + + // if the first base is visible then keep it visible + if(hard_left_edge && getFirstVisibleForwardBase() == 1) + remember_position = 1; + + if(!getSelection().isEmpty()) + { + // but, we want to keep the selection visible after a scale change, so + // that will override the centre position + final Marker first_base_marker = + getSelection().getStartBaseOfSelection(); + + final int first_base_marker_raw_position = + first_base_marker.getRawPosition(); + final int first_base_marker_position; + + if(isRevCompDisplay()) + first_base_marker_position = + getBases().getComplementPosition(first_base_marker_raw_position); + else + first_base_marker_position = first_base_marker_raw_position; + + final Marker last_base_marker = + getSelection().getEndBaseOfSelection(); + + final int last_base_marker_raw_position = + last_base_marker.getRawPosition(); + + final int last_base_marker_position; + + if(isRevCompDisplay()) + last_base_marker_position = + getBases().getComplementPosition(last_base_marker_raw_position); + else + last_base_marker_position = last_base_marker_raw_position; + + final int lowest_visible_base = getFirstVisibleForwardBase(); + final int highest_visible_base = getLastVisibleForwardBase(); + + // first selected base or first visible base, whichever is greater + int restricted_first_selected_base = lowest_visible_base; + + // last selected base or last visible base, whichever is smaller + int restricted_last_selected_base = highest_visible_base; + + if(first_base_marker != null) + { + if(first_base_marker_position > lowest_visible_base && + first_base_marker_position < highest_visible_base) + restricted_first_selected_base = first_base_marker_position; + } + + if(last_base_marker != null) + { + if(last_base_marker_position < highest_visible_base && + last_base_marker_position > lowest_visible_base) + restricted_last_selected_base = last_base_marker_position; + } + + if(getSelection().getMarkerRange() == null) + remember_position = restricted_first_selected_base; + else + { + // keep the centre of the selection in the middle of the display if + // a range of bases is selected + remember_position = restricted_first_selected_base + + (restricted_last_selected_base - + restricted_first_selected_base) / 2; + } + } + + this.scale_factor = scale_factor; + + setScaleValue(); + scale_changer.setValue(scale_factor); + setCentreVisibleForwardBase(remember_position); + fixScrollbar(); + fireAdjustmentEvent(DisplayAdjustmentEvent.SCALE_ADJUST_EVENT); + needVisibleFeatureVectorUpdate(); + + getCanvas().repaint(); + } + } + + /** + * Implementation of the FeatureChangeListener interface. We listen to + * FeatureChange events so that we can update the display if qualifiers + * change. + **/ + public void featureChanged(final FeatureChangeEvent event) + { + final Feature event_feature = event.getFeature(); + + // the feature isn't in an active entry + if(!getEntryGroup().contains(event_feature)) + return; + + // if the feature is visible now or is in the list of visible features + //(ie. it was visible previously) then redisplay. + if(featureVisible(event_feature) || + getVisibleFeatures().contains(event_feature)) + { + // update the visible_features vector + if(getVisibleFeatures().contains(event_feature) && + !featureVisible(event_feature)) + getVisibleFeatures().remove(event_feature); + else + { + // the visibility of the feature has changed + if(!getVisibleFeatures().contains(event_feature) && + featureVisible(event_feature)) + getVisibleFeatures().add(event_feature); + } + + getCanvas().repaint(); + } + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can update the display if entries + * are added or deleted. + **/ + public void entryGroupChanged(final EntryGroupChangeEvent event) + { + switch(event.getType()) + { + case EntryGroupChangeEvent.ENTRY_ADDED: + case EntryGroupChangeEvent.ENTRY_ACTIVE: + case EntryGroupChangeEvent.ENTRY_DELETED: + case EntryGroupChangeEvent.ENTRY_INACTIVE: + if(getOneLinePerEntryFlag()) + fixCanvasSize(); + + needVisibleFeatureVectorUpdate(); + break; + } + + getCanvas().repaint(); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so that we can update the display if features are + * added or deleted. + **/ + public void entryChanged(final EntryChangeEvent event) + { + switch(event.getType()) + { + case EntryChangeEvent.FEATURE_DELETED: + remove(event.getFeature()); + break; + case EntryChangeEvent.FEATURE_ADDED: + add(event.getFeature()); + break; + } + } + + /** + * Implementation of the SelectionChangeListener interface. We listen to + * SelectionChange events so that we can update the list to reflect the + * current selection. + **/ + public void selectionChanged(final SelectionChangeEvent event) + { + // don't bother with events we sent ourself + if(event.getSource() == this) + return; + + needVisibleFeatureVectorUpdate(); + + if(event.getType() == SelectionChangeEvent.SELECTION_CHANGED) + raise_selection_flag = true; + + getCanvas().repaint(); + } + + /** + * Implementation of the SequenceChangeListener interface. + **/ + public void sequenceChanged(final SequenceChangeEvent event) + { + visible_features = new FeatureVector(); + + if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT) + { + final int old_centre_position = getCentreForwardBase(); + + final int new_centre_position = + getBases().getComplementPosition(old_centre_position); + + makeBaseVisibleInternal(new_centre_position, true, false); + } + else + makeBaseVisibleInternal(event.getPosition(), true, false); + + fixScrollbar(); + needVisibleFeatureVectorUpdate(); + getCanvas().repaint(); + + if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT) + fireAdjustmentEvent(DisplayAdjustmentEvent.REV_COMP_EVENT); + else + fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT); + } + + /** + * Invoked when an Option is changed. + **/ + public void optionChanged(OptionChangeEvent event) + { + getCanvas().repaint(); + } + + /** + * Implementation of the GotoListener interface. Invoked when the listener + * should goto to the given base. + **/ + public void performGoto(final GotoEvent event) + { + makeBaseVisible(event.getMarker()); + } + + /** + * Implementation of the DisplayAdjustmentListener interface. Invoked when + * a component(FeatureDisplay) scrolls or changes the scale. Set the + * position and scale of this FeatureDisplay to match the event + **/ + public void displayAdjustmentValueChanged(DisplayAdjustmentEvent event) + { + disable_display_events = true; + try + { + setScaleFactor(event.getScaleFactor()); + setFirstVisibleForwardBase(event.getStart()); + } + finally + { + disable_display_events = false; + } + } + + /** + * Return a MarkerRange that exactly covers the visible bases + **/ + public MarkerRange getVisibleMarkerRange() + { + final int first_base = getFirstVisibleForwardBase(); + final int last_base = getLastVisibleForwardBase(); + + final Strand strand = getBases().getForwardStrand(); + + try + { + return strand.makeMarkerRangeFromPositions(first_base, last_base); + } + catch(OutOfRangeException e) + { + throw new Error("internal error - unexpected exception: " + e); + } + } + + /** + * Arrange for the given feature to be drawn last - ie. so that it will + * appear at the front of the other features. + **/ + void raiseFeature(Feature feature) + { + if(getVisibleFeatures().remove(feature)) + { + getVisibleFeatures().addElementAtEnd(feature); + getCanvas().repaint(); + } + } + + /** + * Arrange for the given feature to be drawn first - ie. so that it will + * appear at the back of the other features. + **/ + void lowerFeature(Feature feature) + { + if(getVisibleFeatures().remove(feature)) + { + getVisibleFeatures().insertElementAt(feature, 0); + getCanvas().repaint(); + } + } + + /** + * Redraw the display with the smallest features on top. Selected features + * will always be on top. + **/ + void smallestToFront() + { + visible_features = new FeatureVector(); + needVisibleFeatureVectorUpdate(); + getCanvas().repaint(); + } + + /** + * Scroll the display so the given base on the Strand currently display on + * top will be in the centre of the display. No events are sent. + * @param base_position The new centre base position. + **/ + private void setCentreVisibleForwardBase(final int base_position) + { + final int max_visible_bases = getMaxVisibleBases(); + final int possible_base_position = base_position - max_visible_bases / 2; + int real_base_position; + + if(possible_base_position < 1 && hard_left_edge) + real_base_position = 1; + else + real_base_position = possible_base_position; + + if(real_base_position > getSequenceLength()) + real_base_position = getSequenceLength(); + + setFirstVisibleForwardBase(real_base_position); + } + + /** + * Scroll the display so that the given base is in the middle of the + * screen. This method obeys the rev_comp_display flag. + * @param base_position The base position to make visible. + * @param forward true means FORWARD - the base_position refers to a + * position on the forward strand, false means REVERSE - the + * base_position refers to the reverse strand. + * @param send_event Send a DisplayAdjustmentEvent if and only if this is + * true + **/ + private void makeBaseVisibleInternal(final int base_position, + final boolean forward, + final boolean send_event) + { + int forward_base_position = base_position; + + if(!forward ^ isRevCompDisplay()) + forward_base_position = + getBases().getComplementPosition(forward_base_position); + + setCentreVisibleForwardBase(forward_base_position); + + if(send_event) + fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT); + } + + /** + * Scroll the display so that the given base is in the middle of the screen + * @param base_marker The Marker of base to make visible. + **/ + private void makeBaseVisible(final Marker base_marker) + { + makeBaseVisibleInternal(base_marker.getPosition(), + base_marker.getStrand().isForwardStrand(), + true); + } + + /** + * Scroll the display so that the given base is in the middle of the screen. + * @param base The base to scroll to. + **/ + public void makeBaseVisible(final int base) + { + makeBaseVisibleInternal(base, true, true); + } + + /** + * This method is called to add a single feature to the display by adding it + * to the vector of visible features(if it is visible that is). + * @param feature The object to add + **/ + private void add(Feature feature) + { + if(getEntryGroup().isActive(feature.getEntry()) && + featureVisible(feature)) + { + if(visible_features.contains(feature)) + { +// throw new Error("internal error - feature added a second time"); + } + else + getVisibleFeatures().addElementAtEnd(feature); + } + + getCanvas().repaint(); + } + + + /** + * The method is called when a Feature should be removed from the display. + * The feature is removed from the vector of visible features. + * @param feature The object to remove + **/ + private void remove(Feature feature) + { + if(visible_features != null) + visible_features.remove(feature); + + getCanvas().repaint(); + } + + /** + * Returns the vector containing those features that are currently visible. + **/ + private FeatureVector getVisibleFeatures() + { + return visible_features; + } + + /** + * Returns a copy of the vector containing those features that are + * currently visible. + **/ + public FeatureVector getCurrentVisibleFeatures() + { + return(FeatureVector)visible_features.clone(); + } + + /** + * Returns a Range that starts at the first visible base and ends at the + * last visible base. + **/ + private Range getVisibleRange() + { + final int first_visible_base = getFirstVisibleForwardBase(); + final int last_visible_base = getLastVisibleForwardBase(); + + if(first_visible_base <= last_visible_base) + return newRange(first_visible_base, last_visible_base); + else + return null; + } + + /** + * This is called after scrolling to add all features that have become + * visible and remove those that have become invisible. The method changes + * visible_features as little as possible so that features that were at the + * end(and hence drawn last/on top) stay there. Selected features and + * segments will always be on top of unselected ones. + **/ + private void updateVisibleFeatureVector() + { + final Range visible_range; + + if(getCanvas().getSize().width == 0) + { + // don't bother doing any thinking + visible_features = new FeatureVector(); + return; + } + + if(raise_selection_flag) + { + final FeatureVector all_features = getSelection().getAllFeatures(); + + for(int i = 0 ; i < all_features.size() ; ++i) + raiseFeature(all_features.elementAt(i)); + + raise_selection_flag = false; + } + + if(isRevCompDisplay()) + { + final int first_visible_base = getFirstVisibleReverseBase(); + final int last_visible_base = getLastVisibleReverseBase(); + visible_range = newRange(first_visible_base, last_visible_base); + } + else + visible_range = getVisibleRange(); + + if(visible_range == null) + { + visible_features = new FeatureVector(); + return; + } + + final FeatureVector real_visible_features = + getSortedFeaturesInRange(visible_range); + + final FeatureVector new_visible_features = new FeatureVector(); + + // add features that are in visible_features and + // real_visible_features - ie features that are still visible + for(int i = 0 ; i < visible_features.size() ; ++i) + { + final Feature new_feature = visible_features.elementAt(i); + if(real_visible_features.contains(new_feature)) + new_visible_features.addElementAtEnd(new_feature); + } + + // add features that are in real_visible_features and not currently + // in visible_features and are not selected(selected features will be + // added last so that they stay on top). + for(int i = 0 ; i < real_visible_features.size() ; ++i) + { + final Feature new_feature = real_visible_features.elementAt(i); + + if(!visible_features.contains(new_feature) && + !getSelection().contains(new_feature)) { + new_visible_features.addElementAtEnd(new_feature); + } + } + + final FeatureVector selection_features = getSelection().getAllFeatures(); + + // now add features that are in real_visible_features, are not in + // visible_features and are selected(selected features are added last so + // that they stay on top). + for(int i = 0 ; i < real_visible_features.size() ; ++i) + { + final Feature new_feature = real_visible_features.elementAt(i); + if(!visible_features.contains(new_feature) && + selection_features.contains(new_feature)) + new_visible_features.addElementAtEnd(new_feature); + } + + visible_features = new_visible_features; + update_visible_features = false; + } + + /** + * This is used by getSortedFeaturesInRange(). + **/ + final private static Comparator feature_comparator = new Comparator() + { + /** + * Compare two Objects with respect to ordering. + * @return a negative number if feature1_object is less than + * feature2_object ; a positive number if feature1_object is greater + * than feature2_object; else 0 + **/ + public int compare(final Object feature1_object, + final Object feature2_object) + { + final Feature feature1 =(Feature) feature1_object; + final Feature feature2 =(Feature) feature2_object; + + final int feature1_size = feature1.getBaseCount(); + final int feature2_size = feature2.getBaseCount(); + + if(feature1_size > feature2_size) + return -1; + else + { + if(feature1_size < feature2_size) + return 1; + else + { + // use hash value as a last resort + if(feature1.hashCode() < feature2.hashCode()) + return -1; + else + { + if(feature1.hashCode() == feature2.hashCode()) + return 0; + else + return 1; + } + } + } + } + }; + + /** + * Return a vector containing the references of the Feature objects in the + * EntryGroup that are within the given range are which should be + * displayed. source features are not returned unless show_source_features + * is true. features with a score less than getMinimumScore() or higher + * than getMaximumScore() aren't returned. + * @param range Return features that overlap this range - ie the start of + * the feature is less than or equal to the end of the range and the end + * of the feature is greater than or equal to the start of the range. + * @return The features the are within the given range. The returned + * object is a copy - changes will not effect the EntryGroup object + * itself. The Features are sorted so that the features with the least + * bases come last, which is the reverse of Entry.getFeaturesInRange(). + **/ + private FeatureVector getSortedFeaturesInRange(final Range range) + { + try + { + FeatureVector features_from_entry = + getEntryGroup().getFeaturesInRange(range); + + final int min_score = getMinimumScore(); + final int max_score = getMaximumScore(); + + final FeatureVector filtered_features = new FeatureVector(); + + // filter out low and high scoring features and(possibly) source + // features + for(int i = features_from_entry.size() - 1 ; i >= 0 ; --i) + { + final Feature this_feature = features_from_entry.elementAt(i); + + if(this_feature.getKey().equals("source") && + !getShowSourceFeatures() && + !getSelection().contains(this_feature)) + continue; + + if(min_score > 0 || max_score < 100) + { + final int this_feature_score = this_feature.getScore(); + + // features with no /score are always shown + if(this_feature_score != -1 && + (this_feature_score < getMinimumScore() || + this_feature_score > getMaximumScore()) && + !getSelection().contains(this_feature)) + continue; + } + + filtered_features.add(this_feature); + } + + features_from_entry = filtered_features; + + final FeatureVector sorted_features = + features_from_entry.sort(feature_comparator); + + return sorted_features; + } + catch(OutOfRangeException e) + { + throw new Error("internal error - unexpected exception: " + e); + } + } + + /** + * The main paint function for the canvas. An off screen image + * used for double buffering when drawing the canvas. + * @param g The Graphics object of the canvas. + **/ + protected void paintCanvas(Graphics g) + { + if(!isVisible()) + return; + + if(update_visible_features) + updateVisibleFeatureVector(); + + fillBackground(g); + + final Selection selection = getSelection(); + final FeatureVector selected_features = selection.getAllFeatures(); + + final FeatureSegmentVector selected_segments = + selection.getSelectedSegments(); + + // we draw the feature backgrounds first then the visible indication + // that there is a MarkerRange selected, then the feature outlines. + for(int i = 0 ; i < getVisibleFeatures().size() ; ++i) + drawFeature(g, getVisibleFeatures().elementAt(i), + true, selected_features, selected_segments); + + drawBaseSelection(g); + drawScale(g); + drawCodons(g); + drawBases(g); + + // now draw the feature outlines + for(int i = 0 ; i < getVisibleFeatures().size() ; ++i) + drawFeature(g, getVisibleFeatures().elementAt(i), + false, selected_features, selected_segments); + } + + /** + * Draw the background colour of the frames. + **/ + private void fillBackground(Graphics g) + { + g.setColor(Color.white); + + g.fillRect(0, 0, getCanvas().getSize().width, + getCanvas().getSize().height); + + if(getOneLinePerEntryFlag()) + { + for(int i = 0 ; i < getEntryGroup().size() ; ++i) + { + final int forward_entry_line = getDisplayLineOfEntryIndex(i, true); + final int reverse_entry_line = getDisplayLineOfEntryIndex(i, false); + + final Entry current_entry = getEntryGroup().elementAt(i); + + if(getEntryGroup().getDefaultEntry() == current_entry && + Options.getOptions().highlightActiveEntryFlag()) + { + fillLane(g, forward_entry_line, active_entry_colour); + fillLane(g, reverse_entry_line, active_entry_colour); + } + else + { + fillLane(g, forward_entry_line, light_grey); + fillLane(g, reverse_entry_line, light_grey); + } + } + } + else + { + if(show_forward_lines) + { + fillLane(g, getFrameDisplayLine(FORWARD_FRAME_1), light_grey); + fillLane(g, getFrameDisplayLine(FORWARD_FRAME_2), light_grey); + fillLane(g, getFrameDisplayLine(FORWARD_FRAME_3), light_grey); + } + + if(show_reverse_lines) + { + fillLane(g, getFrameDisplayLine(REVERSE_FRAME_1), light_grey); + fillLane(g, getFrameDisplayLine(REVERSE_FRAME_2), light_grey); + fillLane(g, getFrameDisplayLine(REVERSE_FRAME_3), light_grey); + } + } + + fillLane(g, getFrameDisplayLine(FORWARD_STRAND), not_so_light_grey); + fillLane(g, getFrameDisplayLine(REVERSE_STRAND), not_so_light_grey); + } + + /** + * Fill one lane/frame line with the given colour. + **/ + private void fillLane(Graphics g, int fill_line_number, Color colour) + { + final int fill_line_top = fill_line_number*getLineHeight() + 1; + + g.setColor(colour); + + final int first_visible_base_coord = + getLowXPositionOfBase(getFirstVisibleForwardBase()); + + final int last_visible_base_coord = + getHighXPositionOfBase(getLastVisibleForwardBase()); + + g.fillRect(first_visible_base_coord, fill_line_top, + last_visible_base_coord - first_visible_base_coord + 1, + getFeatureHeight()); + } + + + /** + * Draw the line showing the base numbers in the middle of the canvas. + * The smallest numbers will be to the left. + **/ + private void drawScale(Graphics g) + { + g.setColor(Color.black); + + final int scale_line = getFrameDisplayLine(SCALE_LINE); + final int scale_number_y_pos = scale_line * getLineHeight(); + + final float bases_per_pixel = + (float)getMaxVisibleBases() / getCanvasWidth(); + + final int base_label_spacing; + + if(getScaleFactor() == 0) + { + // set the spacing so that the labels are at multiples of ten at the + // lowest scale factor + base_label_spacing = + (int) Math.ceil(MINIMUM_LABEL_SPACING * bases_per_pixel / 10) * 10; + } + else + { + // set the spacing so that the labels are at multiples of 100 otherwise + base_label_spacing = + (int) Math.ceil(MINIMUM_LABEL_SPACING * bases_per_pixel / 100) * 100; + } + + final int label_spacing =(int)(base_label_spacing / bases_per_pixel); + + final int possible_index_of_first_label; + + if(isRevCompDisplay()) + possible_index_of_first_label = (getSequenceLength() - + getLastVisibleForwardBase() + 1) / base_label_spacing; + else + possible_index_of_first_label = + getFirstVisibleForwardBase() / base_label_spacing; + + final int index_of_first_label; + + if(possible_index_of_first_label <= 0) + index_of_first_label = 1; + else + index_of_first_label = possible_index_of_first_label; + + final int index_of_last_label; + + if(isRevCompDisplay()) + index_of_last_label = (getSequenceLength() - + getFirstVisibleForwardBase() + 1) / base_label_spacing; + else + index_of_last_label = + getLastVisibleForwardBase() / base_label_spacing; + + for(int i = index_of_first_label; i <= index_of_last_label; ++i) + { + final String label_string= + String.valueOf((int)(i * base_label_spacing)); + final int scale_number_x_pos; + + if(isRevCompDisplay()) + scale_number_x_pos = + getLowXPositionOfBase(getSequenceLength() - + i * base_label_spacing + 1); + else + scale_number_x_pos = + getLowXPositionOfBase(i * base_label_spacing); + + g.setFont(getFont()); + + g.drawString(label_string, + scale_number_x_pos + 2, + scale_number_y_pos + getFontAscent() + 1); + + g.drawLine(scale_number_x_pos, scale_number_y_pos, + scale_number_x_pos, scale_number_y_pos + getLineHeight()); + + if(isRevCompDisplay()) + g.drawLine(scale_number_x_pos, scale_number_y_pos, + scale_number_x_pos + getFontWidth(), scale_number_y_pos); + else + g.drawLine(scale_number_x_pos, + scale_number_y_pos + getLineHeight(), + scale_number_x_pos + getFontWidth(), + scale_number_y_pos + getLineHeight()); + } + } + + /** + * Draw the bases of the forward and reverse strands into a Graphics + * object. + **/ + private void drawBases(Graphics g) + { + if(getScaleFactor() > 1 || + getScaleFactor() == 1 && !show_base_colours) + return; + + final Strand forward_strand; + final Strand reverse_strand; + + if(isRevCompDisplay()) + { + reverse_strand = getBases().getForwardStrand(); + forward_strand = getBases().getReverseStrand(); + } + else + { + forward_strand = getBases().getForwardStrand(); + reverse_strand = getBases().getReverseStrand(); + } + + final Range forward_range = + newRange(getFirstVisibleForwardBase(), + getLastVisibleForwardBase()); + + String forward_visible_bases = + forward_strand.getSubSequence(forward_range).toUpperCase(); + + final int forward_frame_line = getFrameDisplayLine(FORWARD_STRAND); + + final int forward_sequence_length = forward_visible_bases.length(); + + final int offset; + + if(getForwardBaseAtLeftEdge() < 1) + offset = 1 - getForwardBaseAtLeftEdge(); + else + offset = 0; + + + for(int base_index = 0; base_index < forward_sequence_length; + ++base_index) + { + final char char_to_draw = + forward_visible_bases.charAt(base_index); + + if(getScaleFactor() == 0) + drawOneBaseLetter(g, + char_to_draw, + (offset + base_index) * getFontWidth(), + forward_frame_line * getLineHeight()); + else + drawOnePixelBase(g, + char_to_draw, + offset + base_index, + forward_frame_line * getLineHeight()); + } + + final Range reverse_range = newRange(getFirstVisibleReverseBase(), + getLastVisibleReverseBase()); + + String reverse_visible_bases = + reverse_strand.getSubSequence(reverse_range).toUpperCase(); + + final int reverse_frame_line = getFrameDisplayLine(REVERSE_STRAND); + + final int reverse_sequence_length = reverse_visible_bases.length(); + + for(int base_index = 0; base_index < reverse_sequence_length; + ++base_index) + { + final char char_to_draw = + reverse_visible_bases.charAt(reverse_sequence_length - + base_index - 1); + + if(getScaleFactor() == 0) + drawOneBaseLetter(g, + char_to_draw, + (offset + base_index) * getFontWidth(), + reverse_frame_line * getLineHeight()); + else + drawOnePixelBase(g, + char_to_draw, + offset + base_index, + reverse_frame_line * getLineHeight()); + } + } + + + /** + * Draw the codons of the sequence into a graphics object. If the scale is + * 0 then the codon letters will be drawn, otherwise just the stop codons + * will marked. + * @param g The object to draw into. + **/ + private void drawCodons(Graphics g) + { + g.setColor(Color.black); + + if(getScaleFactor() == 0) + { + if(!getOneLinePerEntryFlag()) + { + if(show_forward_lines) + drawForwardCodonLetters(g); + + if(show_reverse_lines) + drawReverseCodonLetters(g); + } + } + else + { + final int MAX_STOP_CODON_SCALE_FACTOR = 7; + if((show_stop_codons || show_start_codons) && + getScaleFactor() <= MAX_STOP_CODON_SCALE_FACTOR) + { + if(!getOneLinePerEntryFlag()) + { + if(show_forward_lines) + drawForwardCodons(g); + + if(show_reverse_lines) + drawReverseCodons(g); + } + } + } + } + + /** + * Mark the start and stop codons into the three forward frame lines. + * @param g The object to draw into. + **/ + private void drawForwardCodons(Graphics g) + { + final Strand strand; + + if(isRevCompDisplay()) + strand = getBases().getReverseStrand(); + else + strand = getBases().getForwardStrand(); + + final int first_visible_base = getForwardBaseAtLeftEdge(); + + final int frame_shift =(first_visible_base - 1) % 3; + + // base to start translation at - we start slightly off the left of the + // screen + final int start_base = first_visible_base - frame_shift; + + // base to end translation at + // we + 3 to the upper bound because partial codons do not get translated + // by getTranslation() + final int end_base = getLastVisibleForwardBase() + 3; + + // not used if show_stop_codons is false + int [][] forward_stop_codons = null; + + if(show_stop_codons) + { + forward_stop_codons = new int [][] { + strand.getStopCodons(newRange(start_base, end_base)), + strand.getStopCodons(newRange(start_base + 1, end_base)), + strand.getStopCodons(newRange(start_base + 2, end_base)) + }; + } + + // not used if show_start_codons is false + int [][] forward_start_codons = null; + + if(show_start_codons) + { + final StringVector start_codons; + + if(Options.getOptions().isEukaryoticMode()) + start_codons = Options.getOptions().getEukaryoticStartCodons(); + else + start_codons = Options.getOptions().getProkaryoticStartCodons(); + + forward_start_codons = new int [][] { + strand.getMatchingCodons(newRange(start_base, end_base), + start_codons), + strand.getMatchingCodons(newRange(start_base + 1, end_base), + start_codons), + strand.getMatchingCodons(newRange(start_base + 2, end_base), + start_codons) + }; + } + + for(int i = 0 ; i < 3 ; ++i) + { + final int frame_line = getFrameDisplayLine(FORWARD_FRAME_1 + i); + + final int frame_x_start = i; + + if(show_start_codons) + { + final int [] this_frame_start_codons = forward_start_codons [i]; + + drawCodonMarkLine(g, + frame_line, + this_frame_start_codons, + FORWARD, + 80); + } + + if(show_stop_codons) + { + final int [] this_frame_stop_codons = forward_stop_codons [i]; + + drawCodonMarkLine(g, + frame_line, + this_frame_stop_codons, + FORWARD, + 100); + } + } + } + + /** + * Mark the stop codons into the three reverse frame lines. + * @param g The object to draw into. + **/ + private void drawReverseCodons(Graphics g) + { + final Strand strand; + + if(isRevCompDisplay()) + strand = getBases().getForwardStrand(); + else + strand = getBases().getReverseStrand(); + + final int first_visible_base = getFirstVisibleReverseBase(); + + final int frame_shift = (first_visible_base - 1) % 3; + + // base to start translation at + final int start_base = first_visible_base - frame_shift; + + // base to end translation at + // we + 3 to the upper bound because partial codons do not get translated + // by getTranslation() + final int end_base = getLastVisibleReverseBase() + 3; + + // not used if show_stop_codons is false + int [][] reverse_stop_codons = null; + + if(show_stop_codons) + { + reverse_stop_codons = new int [][] { + strand.getStopCodons(newRange(start_base, end_base)), + strand.getStopCodons(newRange(start_base + 1, end_base)), + strand.getStopCodons(newRange(start_base + 2, end_base)) + }; + } + + // not used if show_start_codons is false + int [][] reverse_start_codons = null; + + if(show_start_codons) + { + final StringVector start_codons; + + if(Options.getOptions().isEukaryoticMode()) + start_codons = Options.getOptions().getEukaryoticStartCodons(); + else + start_codons = Options.getOptions().getProkaryoticStartCodons(); + + reverse_start_codons = new int [][] { + strand.getMatchingCodons(newRange(start_base, end_base), + start_codons), + strand.getMatchingCodons(newRange(start_base + 1, end_base), + start_codons), + strand.getMatchingCodons(newRange(start_base + 2, end_base), + start_codons) + }; + } + + for(int i = 0 ; i < 3 ; ++i) + { + final int frame_line = getFrameDisplayLine(REVERSE_FRAME_1 - i); + final int frame_x_start = i; + + if(show_start_codons) + { + final int [] this_frame_start_codons = reverse_start_codons [i]; + drawCodonMarkLine(g, + frame_line, + this_frame_start_codons, + REVERSE, + 80); + } + + if(show_stop_codons) + { + final int [] this_frame_stop_codons = reverse_stop_codons [i]; + drawCodonMarkLine(g, + frame_line, + this_frame_stop_codons, + REVERSE, + 100); + } + } + } + + /** + * Draw the codon letters into the three forward frame lines. + * @param g The object to draw into. + **/ + private void drawForwardCodonLetters(Graphics g) + { + final Strand strand; + + if(isRevCompDisplay()) + strand = getBases().getReverseStrand(); + else + strand = getBases().getForwardStrand(); + + final int first_visible_base = getFirstVisibleForwardBase(); + + for(int i = 0 ; i < 3 ; ++i) + { + final int frame_shift = 1 -(first_visible_base + 3 - i) % 3; + + // base to start translation at - we start slightly off the left of the + // screen so that the first partial codon is translated as '.' + final int start_base = first_visible_base + frame_shift; + + // base to end translation at - we end slightly off the right of the + // screen + final int end_base = getLastVisibleForwardBase() + 1; + + final int frame_line = getFrameDisplayLine(FORWARD_FRAME_1 + i); + + int frame_x_start = frame_shift; + + final AminoAcidSequence this_frame_translation = + strand.getTranslation(newRange(start_base, end_base), + false); + + final String this_frame_translation_string = + this_frame_translation.toString(); + + drawCodonLine(g, + frame_x_start, + frame_line, + this_frame_translation_string, + FORWARD); + } + } + + /** + * Draw the codon letters into the three reverse frame lines. + * @param g The object to draw into. + **/ + private void drawReverseCodonLetters(Graphics g) + { + final Strand strand; + + if(isRevCompDisplay()) + strand = getBases().getForwardStrand(); + else + strand = getBases().getReverseStrand(); + + final int first_visible_base = getFirstVisibleReverseBase(); + + for(int i = 0 ; i < 3 ; ++i) + { + final int frame_shift = 1 -(first_visible_base + 3 - i) % 3; + + // base to start translation at - we start slightly off the right of the + // screen so that the first partial codon is translated as '.' + final int start_base = first_visible_base + frame_shift; + + // base to end translation at - we end slightly off the left of the + // screen + final int end_base = getLastVisibleReverseBase() + 1; + + final int frame_line = getFrameDisplayLine(REVERSE_FRAME_1 - i); + + int frame_x_start = frame_shift; + + final AminoAcidSequence this_frame_translation = + strand.getTranslation(newRange(start_base, end_base), + false); + + final String this_frame_translation_string = + this_frame_translation.toString(); + + drawCodonLine(g, + frame_x_start, + frame_line, + this_frame_translation_string, + REVERSE); + } + } + + + /** + * Draw one line of codons. + * @param g The object to draw into. + * @param frame_start When drawing in the FORWARD direction this is the + * offset from the left of the screen at which to start drawing the line. + * For REVERSE it is the offset from the last visible base. + * @param line_number The frame line to draw into. (see + * getFrameDisplayLine() for more details about these line numbers). + * @param codons A String containing the codon letters to draw. + * @param direction The direction to draw the letters in(see the + * frame_start parameter). + **/ + private void drawCodonLine(Graphics g, + int frame_start, + int line_number, + String codons, + int direction) + { + final int offset; + + if(getForwardBaseAtLeftEdge() < 1 && direction != REVERSE) + offset = 1 - getForwardBaseAtLeftEdge(); + else + offset = 0; + + final String upper_case_codons = codons.toUpperCase(); + final int draw_y_position = line_number * getLineHeight(); + + // draw each AA letter + for(int i = 0 ; i < upper_case_codons.length() ; ++i) + { + final char aa_char = upper_case_codons.charAt(i); + + int draw_x_position = + (int)((offset + frame_start + 3 * i + 1) * getScaleValue()); + + if(direction == REVERSE) + { + // draw in the opposite direction + draw_x_position = + getLowXPositionOfBase(getLastVisibleForwardBase()) - + draw_x_position; + } + + drawOneLetter(g, aa_char, draw_x_position, draw_y_position); + } + } + + /** + * Draw one line of codons marks(stop codons or start codons). + * @param g The object to draw into. + * @param line_number The frame line to draw into. (see + * getFrameDisplayLine() for more details about these line numbers). + * @param codon_positions A vector containing the base positions of the + * codons to mark. + * @param direction The direction to draw the letters in(see the + * frame_start parameter). + * @param height_percent The height of the mark as a percentage of the + * frame line height. + **/ + private void drawCodonMarkLine(final Graphics g, + final int line_number, + final int [] codon_positions, + final int direction, + final int height_percent) + { + + final int first_visible_forward_base = getForwardBaseAtLeftEdge(); + final int first_visible_reverse_base = getFirstVisibleReverseBase(); + final int last_visible_forward_base = getLastVisibleForwardBase(); + final double scale_value = getScaleValue(); + + final int line_height = getLineHeight(); + + final Integer colour_number = + Options.getOptions().getIntegerProperty("colour_of_start_codon"); + + final Color start_codon_colour; + + if(colour_number != null) + { + final int int_colour_number = colour_number.intValue(); + start_codon_colour = + Options.getOptions().getColorFromColourNumber(int_colour_number); + } + else + start_codon_colour = Color.blue; + + final int draw_y_position = line_number * line_height + + (int)(1.0 * line_height *(100 - height_percent) / 100 / 2 + 0.5); + + final int mark_height = height_percent * line_height / 100; + + // used to remember the position of the previously drawn codon + int last_x_position = -1; + + for(int i = 0 ; i < codon_positions.length ; ++i) + { + final int codon_base_start = codon_positions[i]; + + // zero is the end of data marker + if(codon_base_start == 0) + break; + + int draw_x_position; + + if(direction == FORWARD) + draw_x_position = getLowXPositionOfBase(codon_base_start); + else + { + final int raw_base_position = + getBases().getComplementPosition(codon_base_start); + draw_x_position = getLowXPositionOfBase(raw_base_position); + } + + // draw only if we haven't drawn on the position already + if(last_x_position == -1 || draw_x_position != last_x_position) + { + if(height_percent < 100) + drawOneCodonMark(g, draw_x_position, draw_y_position, + direction, mark_height, start_codon_colour); + else + drawOneCodonMark(g, draw_x_position, draw_y_position, + direction, mark_height, Color.black); + + last_x_position = draw_x_position; + } + } + } + + /** + * Return the colour in which the given base should be drawn: blue for C, + * red for T, green for A and black for G. + **/ + private static Color getColourOfBase(final char base_char) + { + switch(base_char) + { + case 'c': case 'C': + return Color.blue; + case 't': case 'T': + return Color.red; + case 'a': case 'A': + return dark_green; + case 'g': case 'G': + return Color.black; + default: + return Color.white; + } + } + + + /** + * Draw a letter at a specified position in a Graphics object. + * @param g The object to draw into. + * @param letter The character to draw. + * @param x_pos The x position at which to draw the letter. + * @param y_pos The y position at which to draw the letter. + **/ + private void drawOneLetter(final Graphics g, final char letter, + final int x_pos, final int y_pos) + { + draw_one_char_temp_array[0] = letter; + + g.setFont(getFont()); + g.drawChars(draw_one_char_temp_array, 0, 1, x_pos, + y_pos + getFontAscent() + 1); + } + + /** + * Draw a letter at a specified position in a Graphics object. + * @param g The object to draw into. + * @param base_char The character of the base to draw(A, T, G or C). + * @param x_pos The x position at which to draw the letter. + * @param y_pos The y position at which to draw the letter. + **/ + private void drawOneBaseLetter(final Graphics g, final char base_char, + final int x_pos, final int y_pos) + { + if(show_base_colours) + g.setColor(getColourOfBase(base_char)); + + drawOneLetter(g, base_char, x_pos, y_pos); + } + + + /** + * Draw a codon mark at the given position. The codon is represented + * by a one pixel wide line for scale factor greater than 1 and 3 pixels + * wide if the scale factor is one. + * @param g The object to draw into. + * @param x_pos The x position at which to draw the codon. + * @param y_pos The y position at which to draw the codon. The line + * is drawn down from the given x,y position. + * @param direction If this is FORWARD the line is draw from the y position + * to the right, otherwise it is drawn to the left. + * @param height The height in pixels of the codon mark. + * @param colour The colour to use when drawing the mark. + **/ + private void drawOneCodonMark(final Graphics g, + final int x_pos, final int y_pos, + final int direction, + final int height, + final Color colour) + { + g.setColor(colour); + + if(getScaleFactor() == 1) + { + // draw a line three pixels/bases wide + if(direction == FORWARD) + { + g.drawLine(x_pos + 1, y_pos, + x_pos + 1, y_pos + height - 1); + g.drawLine(x_pos + 2, y_pos, + x_pos + 2, y_pos + height - 1); + } + else + { + g.drawLine(x_pos - 1, y_pos, + x_pos - 1, y_pos + height - 1); + g.drawLine(x_pos - 2, y_pos, + x_pos - 2, y_pos + height - 1); + } + } + g.drawLine(x_pos, y_pos, x_pos, y_pos + height - 1); + } + + /** + * Draw a one pixel wide line representing a single base on the forward or + * reverse strand. The base is coloured according to which base is passed + * to the method: blue for C, red for T, green for A and black for G. + * @param g The object to draw into. + * @param aa_char The character to draw. + * @param x_pos The x position at which to draw the line representing the + * base. + * @param y_pos The y position at which to draw the top of the line + * representing the base. + **/ + private void drawOnePixelBase(Graphics g, char base_char, + int x_pos, int y_pos) + { + g.setColor(getColourOfBase(base_char)); + g.drawLine(x_pos, y_pos, x_pos, y_pos + getLineHeight() - 1); + } + + /** + * Return the line on the canvas where this feature segment should be drawn. + * @param segment The segment in question. + * @return The line to draw into. + **/ + private int getSegmentDisplayLine(FeatureSegment segment) + { + if(getOneLinePerEntryFlag()) + { + final Feature feature = segment.getFeature(); + if((feature.isProteinFeature() || frame_features_flag) && + (show_forward_lines &&(feature.isForwardFeature() ^ + isRevCompDisplay()) || + show_reverse_lines &&(!feature.isForwardFeature() ^ + isRevCompDisplay()))) + { + return getFeatureDisplayLine(feature); + } + else + { + if(feature.isForwardFeature() ^ isRevCompDisplay()) + return getFrameDisplayLine(FORWARD_STRAND); + else + return getFrameDisplayLine(REVERSE_STRAND); + } + } + else + { + final int frame_id = + maybeFlipFrameDirection(getSegmentFrameID(segment)); + + return getFrameDisplayLine(frame_id); + } + } + + /** + * Return the frame ID of to use when drawing the given segment. + **/ + private int getSegmentFrameID(final FeatureSegment segment) + { + final int frame_id = segment.getFrameID(); + + final Feature feature = segment.getFeature(); + + if((feature.isProteinFeature() || frame_features_flag) && + (show_forward_lines &&(feature.isForwardFeature() ^ + isRevCompDisplay())|| + show_reverse_lines &&(! feature.isForwardFeature() ^ + isRevCompDisplay()))) + { + return frame_id; + } + else + { + switch(frame_id) + { + case FORWARD_FRAME_1: + // fall through + case FORWARD_FRAME_2: + // fall through + case FORWARD_FRAME_3: + // fall through + case FORWARD_STRAND: + return FORWARD_STRAND; + case REVERSE_FRAME_1: + // fall through + case REVERSE_FRAME_2: + // fall through + case REVERSE_FRAME_3: + // fall through + case REVERSE_STRAND: + return REVERSE_STRAND; + default: + return frame_id; + } + } + } + + /** + * Return the line on the canvas where the given feature should be drawn + * when one_line_per_entry is true. Call getLineCount() to find out the + * total number of lines. Each line is getLineHeight() pixels high. + * @return The line to draw into. + **/ + private int getFeatureDisplayLine(final Feature feature) + { + final int entry_index = getEntryGroup().indexOf(feature.getEntry()); + return getDisplayLineOfEntryIndex(entry_index, + feature.isForwardFeature() ^ + isRevCompDisplay()); + } + + /** + * Return the line in the display where features from the entry specified + * by the given entry_index should be drawn. + * @param is_forward_feature indicates whether the feature of interest is + * on the forward or reverse strand. + **/ + private int getDisplayLineOfEntryIndex(final int entry_index, + final boolean is_forward_feature) + { + if(is_forward_feature) + { + if(getShowForwardFrameLines()) + { + if(getShowLabels()) + return entry_index * 2; + else + return entry_index; + } + else + return 0; + } + else + { + int return_value = 0; + if(getShowLabels()) + { + return_value = getEntryGroup().size() * 2 + 3 - entry_index * 2; + + if(getShowForwardFrameLines()) + return_value += getEntryGroup().size() * 2; + } + else + { + return_value = getEntryGroup().size() + 2 - entry_index; + + if(getShowForwardFrameLines()) + return_value += getEntryGroup().size(); + } + return return_value; + } + } + + /** + * If rev_comp_display is true return the corresponding frame_id from the + * opposite strand(eg. FORWARD_FRAME_1 gives REVERSE_FRAME_3) otherwise + * return the frame_id unchanged. + **/ + private int maybeFlipFrameDirection(final int frame_id) + { + if(isRevCompDisplay()) + { + // flip the frame so that forward becomes reverse and reverse becomes + // forward + switch(frame_id) + { + case FORWARD_FRAME_1: + return REVERSE_FRAME_1; + case FORWARD_FRAME_2: + return REVERSE_FRAME_2; + case FORWARD_FRAME_3: + return REVERSE_FRAME_3; + case FORWARD_STRAND: + return REVERSE_STRAND; + case REVERSE_FRAME_1: + return FORWARD_FRAME_1; + case REVERSE_FRAME_2: + return FORWARD_FRAME_2; + case REVERSE_FRAME_3: + return FORWARD_FRAME_3; + case REVERSE_STRAND: + return FORWARD_STRAND; + default: + return frame_id; + } + } + return frame_id; + } + + /** + * Return the line on the canvas where this frame ID should be drawn. Call + * getLineCount() to find out the total number of lines. Each line is + * getLineHeight() pixels high. + * @param frame_id The frame ID. + * @return The line to draw into. + **/ + private int getFrameDisplayLine(int frame_id) + { + if(getOneLinePerEntryFlag()) + { + int return_value; + switch(frame_id) + { + case FORWARD_STRAND: + return_value = 0; + break; + case REVERSE_STRAND: + if(show_labels) + return_value = 3; + else + return_value = 2; + break; + case SCALE_LINE: + if(show_labels) + return_value = 2; + else + return_value = 1; + break; + default: + throw new Error("internal error - unexpected value: " + frame_id); + } + + if(show_forward_lines) + { + if(show_labels) + return_value += getEntryGroup().size(); + + return return_value + getEntryGroup().size(); + } + else + return return_value; + } + + final int line_number; + + switch(frame_id) + { + case FORWARD_FRAME_1: + line_number = 0; + break; + case FORWARD_FRAME_2: + if(show_labels) + line_number = 2; + else + line_number = 1; + break; + case FORWARD_FRAME_3: + if(show_labels) + line_number = 4; + else + line_number = 2; + break; + case FORWARD_STRAND: + if(show_forward_lines) + { + if(show_labels) + line_number = 6; + else + line_number = 3; + } + else + line_number = 0; + break; + case REVERSE_STRAND: + if(show_forward_lines) + { + if(show_labels) + line_number = 9; + else + line_number = 5; + } + else + { + if(show_labels) + line_number = 3; + else + line_number = 2; + } + break; + case REVERSE_FRAME_3: + if(show_forward_lines) + { + if(show_labels) + line_number = 11; + else + line_number = 6; + } + else + { + if(show_labels) + line_number = 5; + else + line_number = 3; + } + break; + case REVERSE_FRAME_2: + if(show_forward_lines) + { + if(show_labels) + line_number = 13; + else + line_number = 7; + } + else + { + if(show_labels) + line_number = 7; + else + line_number = 4; + } + break; + case REVERSE_FRAME_1: + if(show_forward_lines) + { + if(show_labels) + line_number = 15; + else + line_number = 8; + } + else + { + if(show_labels) + line_number = 9; + else + line_number = 5; + } + break; + default: + if(show_forward_lines) + { + if(show_labels) + line_number = 8; + else + line_number = 4; + } + else + { + if(show_labels) + line_number = 2; + else + line_number = 1; + } + break; + } + + return line_number; + } + + /** + * Return the number of lines of text we need to fit on the canvas. This + * is used to set the height of the canvas. + **/ + private int getLineCount() + { + int line_count; + + if(show_labels) + line_count = 5; + else + line_count = 3; + + int extra_line_count; + + if(getOneLinePerEntryFlag()) + { + // some number of entry lines + extra_line_count = getEntryGroup().size(); + } + else + { + // three frame line + extra_line_count = 3; + } + + if(show_labels) + extra_line_count *= 2; + + if(show_forward_lines) + line_count += extra_line_count; + + if(show_reverse_lines) + line_count += extra_line_count; + + return line_count; + } + + /** + * Return the vertical offset from the top of the canvas for this feature. + * The returned value will be the y-coordinate of the top of the lane that + * this feature should be draw into. + **/ + private int getSegmentVerticalOffset(FeatureSegment segment) + { + // one "line" is font_height pixels high,(the number of lines times the + // font_height is the height of the height of canvas) + final int line = getSegmentDisplayLine(segment); + + return getLineOffset(line); + } + + /** + * Given a line return the vertical offset(in pixels) from the top of the + * canvas. + **/ + private int getLineOffset(int line) + { + return line * getLineHeight(); + } + + /** + * Return the lowest on screen(with respect to the canvas) x coordinate of + * a base. If the scale_factor is zero then one base will be font_width + * wide and this method will return a different value than + * getHighXPositionOfBase(). If scale_factor is greater than one then + * the two methods will return the same thing. + * @param base_number The(forward) base to calculate the position of. + **/ + private int getLowXPositionOfBase(int base_number) + { + return(int)((base_number - getForwardBaseAtLeftEdge()) * + getScaleValue()); + } + + /** + * Return the highest on screen(ie with respect to the canvas) x + * coordinate of a base. See comment on getLowXPositionOfBase(). + * @param base_number The(forward) base to calculate the position of. + **/ + private int getHighXPositionOfBase(int base_number) + { + if(getScaleFactor() == 0) + return getLowXPositionOfBase(base_number) + getFontWidth() - 1; + else + return getLowXPositionOfBase(base_number); + } + + /** + * Return the low on screen x coordinate of the base at the given Marker + * position. If the scale_factor is zero then one base will be font_width + * wide so that this method will return a different position than + * getHighXPositionOfMarker(). + * @param marker The Marker position to return the screen position of. + **/ + private int getLowXPositionOfMarker(Marker marker) + { + int position = marker.getRawPosition(); + + if(isRevCompDisplay()) + position = getSequenceLength() - position + 1; + + if(marker.getStrand().isForwardStrand() ^ isRevCompDisplay()) + return getLowXPositionOfBase(position); + else + return getHighXPositionOfBase(position); + } + + /** + * Return the high on screen x coordinate of the base at the given Marker + * position. If the scale_factor is zero then one base will be font_width + * wide so that this method will return a different position than + * getLowXPositionOfMarker(). + * @param marker The Marker position to return the screen position of. + **/ + private int getHighXPositionOfMarker(Marker marker) + { + int position = marker.getRawPosition(); + + if(isRevCompDisplay()) + position = getSequenceLength() - position + 1; + + if(marker.getStrand().isForwardStrand() ^ isRevCompDisplay()) + return getHighXPositionOfBase(position); + else + return getLowXPositionOfBase(position); + } + + + /** + * This is the approximate opposite of getLowXPositionOfBase() - it returns + * MarkerRange corresponding to the given screen position. It returns a + * MarkerRange rather than a Marker because if the user clicks on a FRAME + * line then we want to select a whole codon not just one base. + * @param position A position on the canvas. + * @return A MarkerRange covering the clicked on bases. + **/ + private MarkerRange getMarkerRangeFromPosition(Point position) + { + return getMarkerRangeFromPosition(position, true); + } + + /** + * Given a Point and a direction(either FORWARD_STRAND or REVERSE_STRAND), + * return a the corresponding base position on that Strand. + **/ + private int getBasePositionOfPoint(final Point position, + final int direction) + { + if(direction == FORWARD_STRAND) + { + return(int)(1.0 * position.x / getScaleValue() + + getForwardBaseAtLeftEdge()); + } + else + { + if(direction == REVERSE_STRAND) + { + final int raw_base_position = + (int)(1.0 * position.x / getScaleValue() + + getForwardBaseAtLeftEdge()); + return getBases().getComplementPosition(raw_base_position); + } + else + throw new Error("internal error - unexpected value: " + direction); + } + } + + /** + * This is the approximate opposite of getLowXPositionOfBase() - it returns + * MarkerRange corresponding to the given screen position. It returns a + * MarkerRange rather than a Marker because if the user clicks on a FRAME + * line then we want to select a whole codon not just one base. + * @param position A position on the canvas. + * @param whole_codon If true then return a range that covers a whole codon + * if the click was on a frame line. + * @return A MarkerRange covering the clicked on bases. + **/ + private MarkerRange getMarkerRangeFromPosition(final Point position, + final boolean whole_codon) + { + + final int frame_id; + + int base_position; + final Strand strand; + + if(getOneLinePerEntryFlag()) + { + final int scale_line = getFrameDisplayLine(SCALE_LINE); + final int position_line = getLineFromPoint(position); + + if(position_line < scale_line) + { + if(isRevCompDisplay()) + strand = getBases().getReverseStrand(); + else + strand = getBases().getForwardStrand(); + + frame_id = FORWARD_STRAND; + } + else + { + if(position_line > scale_line) + { + if(isRevCompDisplay()) + strand = getBases().getForwardStrand(); + else + strand = getBases().getReverseStrand(); + + frame_id = REVERSE_STRAND; + } + else + return null; + } + + base_position = getBasePositionOfPoint(position, frame_id); + } + else + { + frame_id = getFrameFromPoint(position); + // calculate the frame/strand line and base position + switch(frame_id) + { + case FORWARD_FRAME_1: + case FORWARD_FRAME_2: + case FORWARD_FRAME_3: + case FORWARD_STRAND: + if(isRevCompDisplay()) + strand = getBases().getReverseStrand(); + else + strand = getBases().getForwardStrand(); + + base_position = getBasePositionOfPoint(position, FORWARD_STRAND); + break; + case REVERSE_FRAME_1: + case REVERSE_FRAME_2: + case REVERSE_FRAME_3: + case REVERSE_STRAND: + if(isRevCompDisplay()) + strand = getBases().getForwardStrand(); + else + strand = getBases().getReverseStrand(); + + base_position = getBasePositionOfPoint(position, REVERSE_STRAND); + break; + default: + base_position = 0; + strand = null; + } + } + + final int start_base_position; + final int end_base_position; + + if(whole_codon) + { + start_base_position = + adjustBasePositionForFrame(frame_id, base_position, true); + end_base_position = + adjustBasePositionForFrame(frame_id, base_position, false); + } + else + { + start_base_position = base_position; + end_base_position = base_position; + } + + if(strand == null) + return null; + else + { + try + { + return strand.makeMarkerRangeFromPositions(start_base_position, + end_base_position); + } + catch(OutOfRangeException e) + { + // XXX + return null; + } + } + } + + + /** + * Helper method for getMarkerRangeFromPosition(). Returns a base + * position offset to the start/end of the codon if the given frame id on a + * FRAME. + * @param get_start If true then the base position returned is base + * position of the start of the codon, otherwise the end base is + * returned. + **/ + private int adjustBasePositionForFrame(int frame_id, + final int base_position, + final boolean get_start) + { + final int return_base_position; + final int end_offset; + + if(get_start) + end_offset = 0; + else + { + // the end is two bases ahead of the start + end_offset = 2; + } + + switch(frame_id) + { + case FORWARD_FRAME_1: + case REVERSE_FRAME_1: + { + final int base_pos_mod3 =(base_position - 1) % 3; + return_base_position = base_position + end_offset - base_pos_mod3; + } + break; + case FORWARD_FRAME_2: + case REVERSE_FRAME_2: + { + final int base_pos_mod3 =(base_position - 2) % 3; + return_base_position = base_position + end_offset - base_pos_mod3; + } + break; + case FORWARD_FRAME_3: + case REVERSE_FRAME_3: + { + final int base_pos_mod3 =(base_position - 3) % 3; + return_base_position = base_position + end_offset - base_pos_mod3; + } + break; + default: + return_base_position = base_position; + // do nothing + } + + return return_base_position; + } + + /** + * Draw one feature at the correct place in a Graphics object. + * @param g The Graphics object on which to draw. + * @param draw_feature_fill If true then draw just the solid block of colour + * inside the segments. If false then draw just the outline. + * @param feature The feature to draw. + **/ + private void drawFeature(final Graphics g, + final Feature feature, + final boolean draw_feature_fill, + final FeatureVector selected_features, + final FeatureSegmentVector selected_segments) + { + final FeatureSegmentVector this_feature_segments = feature.getSegments(); + + // don't try to draw a feature with no segments + if(this_feature_segments.size() == 0) + return; + + // set to true if and only if the whole of this feature should be + // highlighted + final boolean highlight_feature_flag; + + if(selected_features.contains(feature)) + { + // ignore the possibility that a feature and a segment from the same + // feature could be in the selection vector at the same time + highlight_feature_flag = true; + } + else + { + // if the feature border flag is off and the feature is not selected + // then don't draw the border + if(!show_feature_borders && !draw_feature_fill) + return; + + highlight_feature_flag = false; + } + + // draw each segment/exon + for(int i = 0 ; i < this_feature_segments.size() ; ++i) + { + final FeatureSegment current_segment = + this_feature_segments.elementAt(i); + final boolean highlight_segment_flag; + + if(selected_segments.indexOf(current_segment) == -1) + highlight_segment_flag = false; + else + highlight_segment_flag = true; + + final boolean draw_direction_arrow_flag; + + // draw an arrow only on the last segment + if(i == this_feature_segments.size() - 1 && show_feature_arrows) + draw_direction_arrow_flag = true; + else + draw_direction_arrow_flag = false; + + drawSegment(g, current_segment, + highlight_feature_flag, highlight_segment_flag, + draw_feature_fill, + draw_direction_arrow_flag); + + if(i + 1 < this_feature_segments.size()) + { + // draw a line between the segments + + final FeatureSegment next_segment = + this_feature_segments.elementAt(i + 1); + + drawSegmentConnection(g, current_segment, next_segment); + } + } + + // draw the label last if the is no label line because in this case the + // label is draw on top of the feature segments + if(draw_feature_fill) + drawFeatureLabel(g, feature); + + Thread.yield(); + } + + /** + * Draw a bent line between two segments which represents the connection + * between exons in feature. + * @param g The Graphics object on which to draw. + * @param lower_segment The reference of the segment that is closest to the + * start of the Strand. The connection line will start at the beginning + * of this segment. + * @param upper_segment The reference of the segment that is furthest from + * the start of the Strand. The connection line will finish at the end + * of this segment. + **/ + private void drawSegmentConnection(Graphics g, + FeatureSegment lower_segment, + FeatureSegment upper_segment) + { + final Marker upper_segment_start_marker = upper_segment.getStart(); + final Marker lower_segment_end_marker = lower_segment.getEnd(); + + int next_segment_start_coord = + getLowXPositionOfMarker(upper_segment_start_marker); + + // make sure we don't wrap around when drawing + if(next_segment_start_coord > 16000) + next_segment_start_coord = 16000; + + // make sure we don't wrap around when drawing + if(next_segment_start_coord < -16000) + next_segment_start_coord = -16000; + + int this_segment_end_coord = + getHighXPositionOfMarker(lower_segment_end_marker); + + // make sure we don't wrap around when drawing + if(this_segment_end_coord > 16000) + this_segment_end_coord = 16000; + + // make sure we don't wrap around when drawing + if(this_segment_end_coord < -16000) + this_segment_end_coord = -16000; + + final int this_segment_vertical_offset = + getSegmentVerticalOffset(lower_segment); + final int next_segment_vertical_offset = + getSegmentVerticalOffset(upper_segment); + + // we draw the line with a bend in the middle - this is the vertical + // position of the bend + final int line_y_position_for_centre; + + if(this_segment_vertical_offset < next_segment_vertical_offset) + line_y_position_for_centre = this_segment_vertical_offset; + else + line_y_position_for_centre = next_segment_vertical_offset; + + final int line_y_position_for_this = + this_segment_vertical_offset + getFeatureHeight() / 2; + final int line_y_position_for_next = + next_segment_vertical_offset + getFeatureHeight() / 2; + + final int horizontal_position_of_centre = + (this_segment_end_coord + next_segment_start_coord) / 2; + + final Feature segment_feature = lower_segment.getFeature(); + final Color feature_colour = segment_feature.getColour(); + + // draw in black if no colour is specified or if the feature is selected + if(feature_colour == null || + getSelection().contains(lower_segment.getFeature())) + g.setColor(Color.black); + else + g.setColor(feature_colour); + + g.drawLine(this_segment_end_coord, + line_y_position_for_this, + horizontal_position_of_centre, + line_y_position_for_centre); + + g.drawLine(horizontal_position_of_centre, + line_y_position_for_centre, + next_segment_start_coord, + line_y_position_for_next); + } + + /** + * Draw a label a feature. This is a helper function for drawFeature(). + * If show_labels is true the labels will be drawn below the features, + * otherwise they will be drawn within the features. + * @param g The Graphics object on which to draw. + * @param feature The feature to draw the label for. + **/ + private void drawFeatureLabel(Graphics g, Feature feature) + { + + // the three frame translation is visible when the scale factor is 0, + // don't draw labels over it + if(!show_labels && getScaleFactor() == 0) + return; + + String label_or_gene = feature.getIDString(); + final String label = feature.getLabel(); + + // special case - don't display a label if the label qualifier is "*" + if(label != null && label.equals("*")) + label_or_gene = ""; + + // don't waste time drawing nothing + if(label_or_gene.length() == 0) + return; + + final FontMetrics fm = g.getFontMetrics(); + final int string_width = fm.stringWidth(label_or_gene); + + final FeatureSegment first_segment = feature.getSegments().elementAt(0); + final int label_x_coord; + + if(feature.isForwardFeature() ^ isRevCompDisplay()) + { + int segment_start_pos = + first_segment.getStart().getRawPosition(); + + if(isRevCompDisplay()) + segment_start_pos = getSequenceLength() - segment_start_pos + 1; + + label_x_coord = getLowXPositionOfBase(segment_start_pos); + } + else + { + int segment_end_pos = + first_segment.getEnd().getRawPosition(); + + if(isRevCompDisplay()) + segment_end_pos = getSequenceLength() - segment_end_pos + 1; + + // reverse the start and end on the complementary strand + label_x_coord = getLowXPositionOfBase(segment_end_pos); + } + + if(label_x_coord >= getCanvas().getSize().width) + return; + +// if(label_x_coord + string_width <= 0) { + // don't draw the label if it is not visible on screen +// } + + + int vertical_offset = + getSegmentVerticalOffset(first_segment); + + if(show_labels) + vertical_offset += getLineHeight(); // move to the label line + + // save this so we can restore it later + final Shape saved_clip = g.getClip(); + + // if we have a labels line draw a white background behind the + // label + if(show_labels) + { + g.setColor(Color.white); + + g.fillRect(label_x_coord - getFontWidth(), + vertical_offset, + string_width + getFontWidth() * 2, + getLineHeight()); + } + else + { + // if there is no label line clip to the size of the first segment + // and draw in there + final int segment_start_coord = + getSegmentStartCoord(first_segment); + final int segment_end_coord = + getSegmentEndCoord(first_segment); + + if(Math.abs(segment_end_coord - segment_start_coord) > 5) + { + if(segment_end_coord > segment_start_coord) + g.setClip(segment_start_coord, vertical_offset, + segment_end_coord - segment_start_coord, + getFeatureHeight()); + else + g.setClip(segment_end_coord, vertical_offset, + segment_start_coord - segment_end_coord, + getFeatureHeight()); + } + else + return; // don't draw small labels if there is no room + } + + g.setColor(Color.black); + g.setFont(getFont()); + g.drawString(label_or_gene, label_x_coord + 1, + vertical_offset + getFontAscent() + 1); + + if(!show_labels) + g.setClip(saved_clip); + } + + /** + * Return the position on the canvas where this segment starts. The + * returned value will be -1 if the position is off the left of the screen + * and will be(width of the canvas) if the position is off the right of + * the screen. + **/ + private int getSegmentStartCoord(FeatureSegment segment) + { + final Marker segment_start_marker = segment.getStart(); + int segment_start_coord = + getLowXPositionOfMarker(segment_start_marker); + + // make sure we don't wrap around when drawing + if(segment_start_coord > getCanvas().getSize().width) + segment_start_coord = getCanvas().getSize().width; + + // make sure we don't wrap around when drawing + if(segment_start_coord < 0) + segment_start_coord = -1; + + return segment_start_coord; + } + + /** + * Return the position on the canvas where this segment ends. The returned + * value will be -1 if the position is off the left of the screen and will + * be(width of the canvas) if the position is off the right of the screen. + **/ + private int getSegmentEndCoord(FeatureSegment segment) + { + final Marker segment_end_marker = segment.getEnd(); + int segment_end_coord = getHighXPositionOfMarker(segment_end_marker); + + // make sure we don't wrap around when drawing + if(segment_end_coord > getCanvas().getSize().width) + segment_end_coord = getCanvas().getSize().width; + + // make sure we don't wrap around when drawing + if(segment_end_coord < 0) + segment_end_coord = -1; + + return segment_end_coord; + } + + /** + * Return true if the base given by the Marker is visible. + **/ + private boolean baseVisible(Marker marker) + { + final int first_visible_base = getForwardBaseAtLeftEdge(); + final int last_visible_base = getLastVisibleForwardBase(); + + int marker_position = marker.getRawPosition(); + + if(isRevCompDisplay()) + marker_position = getBases().getComplementPosition(marker_position); + + if(marker_position < first_visible_base || + marker_position > last_visible_base) + return false; + else + return true; + } + + /** + * Return if and only if the segment is(partly) within the range of bases + * we are currently displaying. + * @param segment The FeatureSegment to check. + **/ + private boolean segmentVisible(FeatureSegment segment) + { + int segment_start_coord = getSegmentStartCoord(segment); + int segment_end_coord = getSegmentEndCoord(segment); + + if(segment_end_coord < 0 && segment_start_coord < 0 || + segment_start_coord >= getCanvas().getSize().width && + segment_end_coord >= getCanvas().getSize().width) + return false; + else + return true; + } + + /** + * Return if and only if some part of a feature is(partly) within the + * range of bases we are currently displaying. + * @param feature The Feature to check. + **/ + private boolean featureVisible(Feature feature) + { + if(getMinimumScore() > 0) + { + final int feature_score = feature.getScore(); + + if(feature_score != -1 && feature_score < getMinimumScore()) + return false; + } + + final FeatureSegmentVector segments = feature.getSegments(); + + for(int i = 0 ; i < segments.size() ; ++i) + { + if(segmentVisible(segments.elementAt(i))) + return true; + } + + return false; + } + + /** + * Draw one FeatureSegment into a Graphics object. + * @param g The Graphics object on which to draw. + * @param segment The feature segment to draw + * @param highlight_feature If true draw an extra thick line + * @param highlight_segment If true draw the segment with a doubly thick + * line. + * @param draw_feature_fill If true then just the solid block of colour + * inside the segments will be drawn. If false then only the feature + * outline is drawn. + * @param draw_arrow If true draw a direction arrow at the end of the + * segment. + **/ + private void drawSegment(Graphics g, FeatureSegment segment, + boolean highlight_feature, + boolean highlight_segment, + boolean draw_feature_fill, + boolean draw_arrow) + { + // not on screen + if(!segmentVisible(segment)) + return; + + final Feature segment_feature = segment.getFeature(); + + final int vertical_offset = getSegmentVerticalOffset(segment) + 1; + + int segment_start_coord = getSegmentStartCoord(segment); + int segment_end_coord = getSegmentEndCoord(segment); + + final int segment_height = getFeatureHeight() - 1; + + // this is 1 if the feature is on the forward strand or on a forward frame + // and -1 otherwise. this used to draw the feature arrow in the right + // direction. + final int feature_direction; + if(segment.getFeature().isForwardFeature() ^ isRevCompDisplay()) + feature_direction = 1; + else + feature_direction = -1; + + final int [] x_points = { + segment_start_coord, segment_end_coord, + segment_end_coord, segment_start_coord + }; + + final int [] y_points = { + vertical_offset, vertical_offset, + vertical_offset + segment_height, vertical_offset + segment_height + }; + + if(draw_feature_fill) + { + final Color feature_colour = segment_feature.getColour(); + + // no colour means draw in white + if(feature_colour == null) + g.setColor(Color.white); + else + g.setColor(feature_colour); + + if(segment_feature.isForwardFeature() ^ isRevCompDisplay()) + { + final int segment_width = segment_end_coord - segment_start_coord + 1; + + g.fillRect(segment_start_coord, vertical_offset, + segment_width, segment_height + 1); + } + else + { + final int segment_width = segment_start_coord - segment_end_coord + 1; + + g.fillRect(segment_end_coord, vertical_offset, + segment_width, segment_height + 1); + } + } + else + { + g.setColor(Color.black); + g.drawPolygon(x_points, y_points, 4); + + if(highlight_feature) // highlight selected features + { + // selected - highlight by drawing a thicker line + + x_points[0] -= feature_direction; x_points[1] += feature_direction; + x_points[2] += feature_direction; x_points[3] -= feature_direction; + y_points[0] -= 1; y_points[1] -= 1; + y_points[2] += 1; y_points[3] += 1; + g.drawPolygon(x_points, y_points, 4); + + x_points[0] -= feature_direction; x_points[1] += feature_direction; + x_points[2] += feature_direction; x_points[3] -= feature_direction; + y_points[0] -= 1; y_points[1] -= 1; + y_points[2] += 1; y_points[3] += 1; + g.drawPolygon(x_points, y_points, 4); + + if(highlight_segment) { + x_points[0] -= feature_direction; x_points[1] += feature_direction; + x_points[2] += feature_direction; x_points[3] -= feature_direction; + y_points[0] -= 1; y_points[1] -= 1; + y_points[2] += 1; y_points[3] += 1; + g.drawPolygon(x_points, y_points, 4); + + x_points[0] -= feature_direction; x_points[1] += feature_direction; + x_points[2] += feature_direction; x_points[3] -= feature_direction; + y_points[0] -= 1; y_points[1] -= 1; + y_points[2] += 1; y_points[3] += 1; + g.drawPolygon(x_points, y_points, 4); + } + } + + if(draw_arrow) + { + // now draw the arrow point + final int arrow_tip_x = + x_points[1] + feature_direction * getFontWidth() * 8 / 10; + final int arrow_tip_y =(y_points[1] + y_points[2]) / 2; + + g.drawLine(x_points[1], y_points[1], arrow_tip_x, arrow_tip_y); + g.drawLine(arrow_tip_x, arrow_tip_y, x_points[2], y_points[2]); + } + } + } + + /** + * Draw/highlight the selected range of bases on the FORWARD_STRAND or + * REVERSE_STRAND lines. + **/ + private void drawBaseSelection(Graphics g) + { + final MarkerRange selection_range = getSelection().getMarkerRange(); + + if(selection_range == null) + return; + + final Marker raw_start_base; + final Marker raw_end_base; + + if(selection_range.getStrand().isForwardStrand()) + { + raw_start_base = selection_range.getRawStart(); + raw_end_base = selection_range.getRawEnd(); + } + else + { + raw_start_base = selection_range.getRawEnd(); + raw_end_base = selection_range.getRawStart(); + } + + final int start_coord = getLowXPositionOfMarker(raw_start_base); + final int end_coord = getHighXPositionOfMarker(raw_end_base); + + // not on screen + if(start_coord > getCanvas().getSize().width && + end_coord > getCanvas().getSize().width) + return; + + // not on screen + if(start_coord < 0 && end_coord < 0) + return; + + // the ID of the strand on which to draw the highlighting - this will be + // FORWARD_STRAND or REVERSE_STRAND. + final int strand_id; + + if(selection_range.getStrand().isForwardStrand() ^ + isRevCompDisplay()) + strand_id = FORWARD_STRAND; + else + strand_id = REVERSE_STRAND; + + if(!getOneLinePerEntryFlag()) + { + // the ID of the frame on which to draw the highlighting - this will be + // FORWARD_FRAME_1, 2 or 3 or REVERSE_FRAME_1, 2 or 3. + final int frame_id; + + if(selection_range.getStrand().isForwardStrand() ^ + isRevCompDisplay()) + { + switch((selection_range.getStart().getPosition() - 1) % 3) + { + case 0: + frame_id = FORWARD_FRAME_1; + break; + case 1: + frame_id = FORWARD_FRAME_2; + break; + case 2: + frame_id = FORWARD_FRAME_3; + break; + default: + frame_id = NO_FRAME; + } + } + else + { + switch((selection_range.getStart().getPosition() - 1) % 3) + { + case 0: + frame_id = REVERSE_FRAME_1; + break; + case 1: + frame_id = REVERSE_FRAME_2; + break; + case 2: + frame_id = REVERSE_FRAME_3; + break; + default: + frame_id = NO_FRAME; + } + } + + if(show_forward_lines && strand_id == FORWARD_STRAND || + show_reverse_lines && strand_id == REVERSE_STRAND) + drawBaseRange(g, start_coord, end_coord, frame_id, Color.pink); + } + + drawBaseRange(g, start_coord, end_coord, strand_id, Color.yellow); + } + + /** + * Draw a rectangle representing the currently selected bases. + * @param g The Graphics object on which to draw. + * @param start_coord The start x coordinate. + * @param start_coord The end x coordinate. + * @param frame_id The ID of the frame line where the box should be drawn. + * @param fill_colour The colour to use to draw the box(the border will be + * black). + **/ + private void drawBaseRange(Graphics g, + int start_coord, int end_coord, + int frame_id, + Color fill_colour) + { + if(start_coord < -1) + start_coord = -1; + + if(end_coord < -1) + end_coord = -1; + + if(end_coord > getCanvasWidth()) + end_coord = getCanvasWidth(); + + if(start_coord > getCanvasWidth()) + start_coord = getCanvasWidth(); + + final int frame_line = getFrameDisplayLine(frame_id); + + final int frame_line_y_coord = getLineOffset(frame_line); + + final int [] x_points = { + start_coord, end_coord, end_coord, start_coord + }; + + final int [] y_points = { + frame_line_y_coord, frame_line_y_coord, + frame_line_y_coord + getFeatureHeight() + 1, + frame_line_y_coord + getFeatureHeight() + 1 + }; + + g.setColor(fill_colour); + + if(start_coord > end_coord) + g.fillRect(end_coord, frame_line_y_coord + 1, + start_coord - end_coord + 1, getFeatureHeight()); + else + g.fillRect(start_coord, frame_line_y_coord + 1, + end_coord - start_coord + 1, getFeatureHeight()); + + g.setColor(Color.black); + g.drawPolygon(x_points, y_points, 4); + } + + /** + * Return the number of button the user has down. + **/ + private int buttonDownCount(final MouseEvent event) + { + int count = 0; + if((event.getModifiers() & InputEvent.BUTTON1_MASK) > 0) + ++count; + + if((event.getModifiers() & InputEvent.BUTTON2_MASK) > 0) + ++count; + + if((event.getModifiers() & InputEvent.BUTTON3_MASK) > 0) + ++count; + + return count; + } + + /** + * Add mouse and key listeners to the canvas. + **/ + private void addListeners() + { + getCanvas().addMouseListener(new MouseAdapter() + { + /** + * Listen for mouse press events so that we can do popup menus and + * selection. + **/ + public void mousePressed(MouseEvent event) + { + // finish dragging if the user presses any other button because + // we may not get a mouseReleased() call on some platforms + if(click_segment_marker != null) + { + getEntryGroup().getActionController().endAction(); + click_segment_marker = null; + } + + //ignore + if(buttonDownCount(event) > 1) + return; + + last_mouse_press_event = event; + handleCanvasMousePress(event); + + if(isMenuTrigger(event)) + { + final FeaturePopup popup = + new FeaturePopup(FeatureDisplay.this, + getEntryGroup(), + getSelection(), + getGotoEventSource(), + getBasePlotGroup()); + final JComponent parent = (JComponent)event.getSource(); + + parent.add(popup); + popup.show(parent, event.getX(), event.getY()); + } + } + + /** + * Listen for mouse release events so that we can call endAction(). + **/ + public void mouseReleased(MouseEvent event) + { + last_mouse_press_event = null; + + if(click_segment_marker != null) + { + getEntryGroup().getActionController().endAction(); + click_segment_marker = null; + } + } + }); + + // Listen for mouse motion events so that we can select ranges of bases. + getCanvas().addMouseMotionListener(new MouseMotionAdapter() { + public void mouseDragged(MouseEvent event) + { + if(last_mouse_press_event != null && + !isMenuTrigger(last_mouse_press_event)) + handleCanvasMouseDrag(event); + } + }); + + getCanvas().addKeyListener(new KeyAdapter() { + public void keyPressed(final KeyEvent event) + { + handleKeyPress(FeatureDisplay.this, event); + } + }); + } + + /** + * Handle a mouse press event on the drawing canvas - select on click, + * select and broadcast it on double click. + **/ + private void handleCanvasMousePress(MouseEvent event) + { + if(event.getID() != MouseEvent.MOUSE_PRESSED) + return; + + getCanvas().requestFocus(); + + if(event.getClickCount() == 2) + handleCanvasDoubleClick(event); + else + handleCanvasSingleClick(event); + + getCanvas().repaint(); + } + + /** + * Handle a double click on the canvas. + **/ + private void handleCanvasDoubleClick(MouseEvent event) + { + if(isMenuTrigger(event)) + return; + + if((event.getModifiers() & InputEvent.BUTTON2_MASK) != 0 || + event.isAltDown()) + { + + final Selectable clicked_thing = getThingAtPoint(event.getPoint()); + + if(clicked_thing == null) + { + // select the ORF around the click position + final MarkerRange new_click_range = + getMarkerRangeFromPosition(event.getPoint()); + + if(new_click_range == null) + return; + + final MarkerRange orf_range = + Strand.getORFAroundMarker(new_click_range.getStart(), true); + + // user clicked on a stop codon + if(orf_range == null) + return; + else + getSelection().setMarkerRange(orf_range); + } + else + { + // edit the selected Feature + + final Feature clicked_feature = getFeatureOf(clicked_thing); + + getSelection().set(clicked_feature); + + if(Options.readWritePossible()) + new FeatureEdit(clicked_feature, getEntryGroup(), + getSelection(), + getGotoEventSource()).show(); + } + } + + makeSelectionVisible(); + } + + /** + * Handle a single click on the canvas. + **/ + private void handleCanvasSingleClick(MouseEvent event) + { + click_segment_marker = null; + + final Selectable clicked_thing = getThingAtPoint(event.getPoint()); + + // treate alt modifier like pressing button 2 + if(clicked_thing == null || + (event.getModifiers() & InputEvent.BUTTON2_MASK) != 0) + { + + // if the user presses the mouse button 2 on feature or segment we treat + // it like the feature/segment isn't there + + // if the user hasn't clicked on anything then don't change the + // selection + if(isMenuTrigger(event)) + return; + + // something is selected but the user clicked on nothing with the + // shift key down - do nothing + if(event.isShiftDown() && + getSelection().getAllFeatures().size() > 0) + return; + + final MarkerRange old_selected_range = getSelection().getMarkerRange(); + + final MarkerRange new_click_range = + getMarkerRangeFromPosition(event.getPoint()); + + if(new_click_range == null) + { + click_range = null; + getSelection().clear(); + return; + } + + final MarkerRange new_selection_range; + + if(!event.isShiftDown() || old_selected_range == null) + new_selection_range = new_click_range; + else + { + if(old_selected_range.getStrand() == + new_click_range.getStrand()) + { + // extend the selection + new_selection_range = + old_selected_range.combineRanges(new_click_range, true); + } + else + new_selection_range = old_selected_range; + } + + getSelection().setMarkerRange(new_selection_range); + click_range = new_selection_range; + + } + else + { + // can't select a feature and a MarkerRange + if(getSelection().getMarkerRange() != null && + event.isShiftDown()) + return; + + // clear the MarkerRange because this isn't the start of a range drag + click_range = null; + + getSelection().setMarkerRange(null); + final Feature clicked_feature = getFeatureOf(clicked_thing); + raiseFeature(clicked_feature); + + if(clicked_thing instanceof Feature) + { + // toggle the feature in or out of the selection unless this event + // is a popup trigger in which case we should just make sure the + // feature is in the selection + if(getSelection().contains(clicked_feature)) + { + if(! isMenuTrigger(event)) + { + // change the selection only if the user isn't popping up a menu + if(event.isShiftDown()) + getSelection().remove(clicked_feature); + else + getSelection().set(clicked_feature); + } + } + else + { + if(event.isShiftDown()) + getSelection().add(clicked_feature); + else + getSelection().set(clicked_feature); + } + } + else + { + // must be a FeatureSegment + + // toggle the feature segment in or out of the selection unless + // this event is a popup trigger in which case we should just make + // sure the segment is in the selection + final FeatureSegment clicked_segment = + (FeatureSegment) clicked_thing; + + final FeatureSegmentVector selected_feature_segments = + getSelection().getSelectedSegments(); + + if(selected_feature_segments.contains(clicked_segment)) + { + if(! isMenuTrigger(event)) + { + if(event.isShiftDown()) + getSelection().remove(clicked_segment); + else + getSelection().set(clicked_segment); + } + } + else + { + if(event.isShiftDown()) + { + final FeatureVector selected_features = + getSelection().getSelectedFeatures(); + + if(selected_features.contains(clicked_feature)) + getSelection().remove(clicked_feature); + else + getSelection().add(clicked_segment); + } + else + getSelection().set(clicked_segment); + } + } + + if(Options.getOptions().canDirectEdit() && + !clicked_feature.isReadOnly()) + { + // check to see if the click was on a marker, the second argument is + // false because we want the range to cover just one base + + final MarkerRange new_click_range = + getMarkerRangeFromPosition(event.getPoint(), false); + + // 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 i = 0 ; i < segments.size() ; ++i) + { + final FeatureSegment this_segment = segments.elementAt(i); + + if(new_click_range.getStart().equals(this_segment.getStart()) && + this_segment.canDirectEdit()) + { + click_segment_marker = this_segment.getStart(); + click_segment_marker_is_start_marker = true; + other_end_of_segment_marker = this_segment.getEnd(); + getEntryGroup().getActionController().startAction(); + break; + } + + if(new_click_range.getEnd().equals(this_segment.getEnd()) && + this_segment.canDirectEdit()) + { + click_segment_marker = this_segment.getEnd(); + click_segment_marker_is_start_marker = false; + other_end_of_segment_marker = this_segment.getStart(); + getEntryGroup().getActionController().startAction(); + break; + } + } + } + } + } + + /** + * This is a helper method for handleCanvasSingleClick() and + * handleCanvasDoubleClick(). + * @param object The should be an instance of Feature or FeatureSegment. + * @return If the the argument is a Feature it is returned, if it is a + * FeatureSegment the owning Feature is returned. + **/ + private Feature getFeatureOf(final Object object) + { + if(object instanceof Feature) + return(Feature) object; + else + // object must be a FeatureSegment + return((FeatureSegment) object).getFeature(); + } + + /** + * Handle a mouse drag that started on a Marker from a FeatureSegment. + **/ + private void handleMarkerDrag(final MouseEvent event) + { + MarkerRange drag_range = + getMarkerRangeFromPosition(event.getPoint(), false); + + if(drag_range == null) + return; + + final int new_position; + + 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); + + if(!baseVisible(click_segment_marker)) + makeBaseVisible(click_segment_marker); + } + catch(OutOfRangeException e) + { + throw new Error("internal error - unexpected OutOfRangeException"); + } + } + + /** + * Handle a mouse drag event on the drawing canvas. + **/ + private void handleCanvasMouseDrag(final MouseEvent event) + { + if(event.isShiftDown() && + getSelection().getAllFeatures().size() > 0) + return; + + if(click_segment_marker != null) + { + handleMarkerDrag(event); + return; + } + + MarkerRange drag_range = getMarkerRangeFromPosition(event.getPoint()); + + if(drag_range == null) + return; + + final MarkerRange selected_range = getSelection().getMarkerRange(); + + // if the start and current positions of the drag are not on the + // same Strand then ignore this event + if(selected_range != null && + drag_range.getStrand() != selected_range.getStrand()) + return; + + try + { + // user has dragged off the screen so use a marker at position 1 + if(drag_range.getStart().getPosition() < 1) + drag_range = new MarkerRange(drag_range.getStrand(), 1, 1); + + // user has dragged off the screen so use a marker at position the + // end of the sequence + if(drag_range.getEnd().getPosition() > getSequenceLength()) + drag_range = new MarkerRange(drag_range.getStrand(), + getSequenceLength(), + getSequenceLength()); + } + catch(OutOfRangeException e) + { + throw new Error("internal error - unexpected OutOfRangeException"); + } + + final boolean start_base_is_visible = baseVisible(drag_range.getStart()); + final boolean end_base_is_visible = baseVisible(drag_range.getEnd()); + + if(!start_base_is_visible) + makeBaseVisible(drag_range.getStart()); + + if(!end_base_is_visible) + makeBaseVisible(drag_range.getEnd()); + + // scrolling was necessary so update the visible features vector + if(!start_base_is_visible || !end_base_is_visible) + needVisibleFeatureVectorUpdate(); + + final MarkerRange new_marker_range; + + if(click_range == null) + new_marker_range = drag_range; + else + new_marker_range = selected_range.combineRanges(drag_range, true); + + getSelection().setMarkerRange(new_marker_range); + + getCanvas().repaint(); + } + + /** + * Return the reference of the Object at p on the canvas or null if there + * is none. The returned Object will be a Feature or a FeatureSegment. + **/ + private Selectable getThingAtPoint(Point p) + { + final int line_of_point = getLineFromPoint(p); + + // point is not on a forward or reverse frame or strand + if(line_of_point == -1) + return null; + + // go through the features in reverse order because the feature that is + // drawn last will be on top + for(int i = getVisibleFeatures().size() - 1 ; i >= 0 ; --i) + { + final Feature current_feature = getVisibleFeatures().elementAt(i); + final FeatureSegmentVector segments = current_feature.getSegments(); + + for(int segment_index = 0; segment_index < segments.size(); + ++segment_index) + { + final FeatureSegment current_segment = + segments.elementAt(segment_index); + + final int line_of_segment = getSegmentDisplayLine(current_segment); + + if(line_of_segment == line_of_point || + show_labels && line_of_segment + 1 == line_of_point) + { + // this segment is in the right frame so check that it is between + // the start and end positions of the segment + + if(p.x >= getSegmentStartCoord(current_segment) && + p.x <= getSegmentEndCoord(current_segment) || + p.x <= getSegmentStartCoord(current_segment) && + p.x >= getSegmentEndCoord(current_segment)) + { + final Feature segment_feature = current_segment.getFeature(); + + // special case - if there is only one segment then select the + // whole feature + if(segment_feature.getSegments().size() == 1) + return segment_feature; + else + return current_segment; + } + } + } + } + + return null; + } + + /** + * Create and add a new scroll bar to this FeatureDisplay. + **/ + private void createScrollbar(final boolean scrollbar_at_top) + { + scrollbar = new JScrollBar(Scrollbar.HORIZONTAL); + scrollbar.addAdjustmentListener(new AdjustmentListener() + { + public void adjustmentValueChanged(AdjustmentEvent e) + { + if(left_edge_base != e.getValue()) + { + if(e.getValue() < getSequenceLength()) + left_edge_base = e.getValue(); + else + { + if(left_edge_base == getSequenceLength()) + return; + else + left_edge_base = getSequenceLength(); + } + + fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT); + + needVisibleFeatureVectorUpdate(); + getCanvas().repaint(); + } + } + }); + + if(scrollbar_at_top) + getMidPanel().add(scrollbar, "North"); + else + getMidPanel().add(scrollbar, "South"); + + fixScrollbar(); + fireAdjustmentEvent(DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT); + } + + /** + * Send a DisplayAdjustmentEvent with the current start and end base to + * each of the listeners. This has package scope because EntryEdit + * components need to send this event when a new BasePlot component is + * added. The BasePlot needs to know the first visible base, last visible + * base and the maximum number of visible bases before the plot can be + * drawn. + * @param type the type of event: DisplayAdjustmentEvent.SCALE_ADJUST_EVENT, + * DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT or + * DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT. + **/ + void fireAdjustmentEvent(final int type) + { + if(disable_display_events) + return; + + final DisplayAdjustmentEvent event = + new DisplayAdjustmentEvent(this, + getForwardBaseAtLeftEdge(), + getLastVisibleForwardBase(), + getMaxVisibleBases(), + getScaleValue(), getScaleFactor(), + isRevCompDisplay(), type); + + fireAction(adjustment_listener_list, event); + } + + /** + * Send an event to those object listening for it. + * @param listeners A Vector of the objects that the event should be sent + * to. + * @param event The event to send + **/ + private void fireAction(Vector listeners, ChangeEvent event) + { + final Vector targets; + // copied from a book - synchronizing the whole method might cause a + // deadlock + synchronized(this) + { + targets =(Vector) listeners.clone(); + } + + for(int i = 0; i < targets.size(); ++i) + { + ChangeListener change_listener = (ChangeListener)targets.elementAt(i); + + final DisplayAdjustmentListener target = + (DisplayAdjustmentListener)change_listener; + target.displayAdjustmentValueChanged((DisplayAdjustmentEvent)event); + } + } + + /** + * Create the scroll bar used for changing the scale and add it to the + * FeatureDisplay. + **/ + private void createScaleScrollbar() + { + scale_changer = new JScrollBar(Scrollbar.VERTICAL); + // try to arrange for the scrollbar to have a maximum value big enough + // that the whole sequence can be visible at once + final int MAX_FACTOR = + (int)Math.round(Math.log(getSequenceLength()/20) / Math.log(3)); + scale_changer.setValues(getScaleFactor(), 1, 0, MAX_FACTOR); + scale_changer.setBlockIncrement(1); + scale_changer.setUnitIncrement(1); + scale_changer.addAdjustmentListener(new AdjustmentListener() + { + public void adjustmentValueChanged(AdjustmentEvent e) + { + setScaleFactor(e.getValue()); + } + }); + + add(scale_changer, "East"); + + if(scale_factor >= MAX_FACTOR) + setScaleFactor(MAX_FACTOR - 1); + } + + /** + * Update the parameters of the scrollbar taking changes to the entry_group + * into account. + **/ + private void fixScrollbar() + { + if(scrollbar == null) + return; + + final int sequence_length = getSequenceLength(); + + final int max_visible_bases; + + // make sure max_visible_bases is at least 1 to stop setBlockIncrement() + // from complaining + if(getMaxVisibleBases() > 0) + max_visible_bases = getMaxVisibleBases(); + else + max_visible_bases = 1; + + // initial_value, visible, minimum, maximum + scrollbar.setValues(getForwardBaseAtLeftEdge(), + max_visible_bases, + hard_left_edge ? 1 : -max_visible_bases / 2, + sequence_length + max_visible_bases / 2); + + scrollbar.setBlockIncrement(max_visible_bases); + + if(max_visible_bases >= 10 && getScaleFactor() > 0) + scrollbar.setUnitIncrement(max_visible_bases / 10); + else + scrollbar.setUnitIncrement(1); + } + + /** + * Set the size of the canvas, taking the value of font_height and + * show_label into account. + **/ + private void fixCanvasSize() + { + final int new_width = getCanvas().getSize().width; + final int new_height = getLineHeight() * getLineCount(); + + if(new_height != getCanvas().getSize().width || + new_width != getCanvas().getSize().height) + { + final Dimension preferred_size = + new Dimension(getCanvas().getSize().width, new_height); + getCanvas().setPreferredSize(preferred_size); + + final Dimension minimum_size = new Dimension(1, new_height); + getCanvas().setMinimumSize(minimum_size); + + getCanvas().revalidate(); + getCanvas().repaint(); + } + } + + /** + * Returns the base length of the sequence we are displaying in this + * component. + **/ + public int getSequenceLength() + { + return getEntryGroup().getSequenceLength(); + } + + /** + * Set the first visible base in the forward direction (set the base on the + * forward strand that is on the left of the canvas). This method will + * scroll the display so that this base is is at the very left hand side of + * the canvas. + **/ + private void setFirstVisibleForwardBase(int new_position) + { + if(left_edge_base != new_position) + { + left_edge_base = new_position; + if(scrollbar != null) + scrollbar.setValue(new_position); + + needVisibleFeatureVectorUpdate(); + getCanvas().repaint(); + } + } + + /** + * Return the height each line of the display should be. Each frame will + * be drawn into one line. + **/ + private int getLineHeight() + { + return getFontHeight(); + } + + /** + * Return the current scale factor. The scale factor is a number greater + * than or equal to zero than controls the number of bases that can appear + * on screen. See getScaleValue(). + **/ + public int getScaleFactor() + { + return scale_factor; + } + + /** + * Return the ID of the first(ie. top) line of the display. + **/ + private int getFirstLineID() + { + if(show_forward_lines) + return FORWARD_FRAME_1; + else + return FORWARD_STRAND; + } + + /** + * Return the ID of the last(ie. bottom) line of the display. + **/ + private int getLastLineID() + { + if(show_reverse_lines) + return REVERSE_FRAME_1; + else + return REVERSE_STRAND; + } + + /** + * Return the display line that contains the given point, or -1 if + * the point is off screen. + **/ + private int getLineFromPoint(final Point point) + { + if(point.y >= getCanvasHeight()) + return -1; + + final int return_value = point.y / getLineHeight(); + + if(return_value < 0) + return -1; + else + return return_value; + } + + /** + * Return the ID of the frame at the given Point in the canvas or NO_FRAME + * if the Point is not within a frame. If labels are on then points on the + * label lines get returned as the frame id of the STRAND or FRAME line + * above. + **/ + private int getFrameFromPoint(final Point point) + { + if(getOneLinePerEntryFlag()) + { + // there are no frame lines so just look at the strand lines + final int line_point = getLineFromPoint(point); + if(line_point == getFrameDisplayLine(FORWARD_STRAND) ^ + isRevCompDisplay()) + return FORWARD_STRAND; + + if(line_point == getFrameDisplayLine(REVERSE_STRAND) ^ + isRevCompDisplay()) + return REVERSE_STRAND; + + // fall through + } + else + { + final int start_frame = getFirstLineID(); + final int end_frame = getLastLineID(); + + for(int i = start_frame ; i <= end_frame ; ++i) { + final int top_of_line = getLineOffset(getFrameDisplayLine(i)); + final int line_height; + + if(show_labels) + line_height = getLineHeight() * 2; + else + line_height = getLineHeight(); + + if(point.y >= top_of_line && + point.y < top_of_line + line_height) + return i; + } + } + // the point isn't in any or the frames + return NO_FRAME; + } + + /** + * Return the base number of the first visible base displayed in the + * forward direction, ie the base on the forward(or top strand) that is on + * the left each of the canvas. Note that the strand that is currently + * being display in the forward direction will be the reverse strand if + * rev_comp_display is true. The number returned will always be > 1 and < + * the sequence length. If hard_left_edge is true this method will always + * return the same as getForwardBaseAtLeftEdge*(. + **/ + public int getFirstVisibleForwardBase() + { + if(left_edge_base < 1) + return 1; + else + return left_edge_base; + } + + /** + * Return the base number of the first visible base displayed in the + * forward direction, ie the base on the forward(or top strand) that is on + * the left each of the canvas. Note that the strand that is currently + * being display in the forward direction will be the reverse strand if + * rev_comp_display is true. + **/ + public int getForwardBaseAtLeftEdge() + { + return left_edge_base; + } + + /** + * Return a base marker of the first visible base in the forward + * direction, ie the base on the forward strand that is on the left + * each of the canvas. See comments on getFirstVisibleForwardBase(). + **/ + private Marker getFirstVisibleForwardBaseMarker() + { + try + { + final Strand strand; + if(isRevCompDisplay()) + strand = getBases().getReverseStrand(); + else + strand = getBases().getForwardStrand(); + + return strand.makeMarker(getFirstVisibleForwardBase()); + } + catch(OutOfRangeException e) + { + throw new Error("internal error - unexpected OutOfRangeException"); + } + } + + /** + * Return the base number of the last visible base in the forward + * direction, ie the base on the forward strand that is on the right + * edge of the canvas. The number returned will always be > 1 and < the + * sequence length. + **/ + public int getLastVisibleForwardBase() + { + final int possible_last_base = + (int)(getForwardBaseAtLeftEdge() + getMaxVisibleBases()); + + if(possible_last_base > getSequenceLength()) + return getSequenceLength(); + else + { + if(possible_last_base > 0) + return possible_last_base; + else + return 1; + } + } + + /** + * Return the base number of the first visible base in the reverse + * direction , ie the base on the reverse strand that is on the left + * each of the canvas. + **/ + private int getFirstVisibleReverseBase() + { + final int last_forward_base = getLastVisibleForwardBase(); + return getBases().getComplementPosition(last_forward_base); + } + + /** + * Return the base number of the last visible base in the reverse + * direction, ie the base on the reverse strand that is on the right + * edge of the canvas. + **/ + private int getLastVisibleReverseBase() + { + // XXX + final int first_forward_base = getFirstVisibleForwardBase(); + return getBases().getComplementPosition(first_forward_base); + } + + /** + * Return the base of the forward Strand of the sequence that is closest to + * the centre of the view. + **/ + private int getCentreForwardBase() + { + int possible_return_position = + getForwardBaseAtLeftEdge() + getMaxVisibleBases() / 2; + + int return_position; + + if(possible_return_position < getSequenceLength()) + return_position = possible_return_position; + else + return_position = getSequenceLength(); + + return return_position; + } + + /** + * Return the amount display is scaled. The value is 3 to the power of + * -(scale_factor - 1), except for a scale_factor of zero which gives a + * scale value of font_width. + **/ + float getScaleValue() + { + return scale_value; + } + + /** + * Return the height in pixels we should use for drawing features. + **/ + private int getFeatureHeight() + { + // don't use getLineHeight() because we want a nice space between + // frames/lines + return getFontAscent() + 2; + } + + /** + * Return the number of bases we can fit on screen at once, ie the number + * that will fit side by side on the canvas. + **/ + public int getMaxVisibleBases() + { + return(int)(getCanvasWidth() / getScaleValue()); + } + + /** + * Return the Bases object of the EntryGroup that this FeatureDisplay is + * displaying. + **/ + public Bases getBases() + { + return getEntryGroup().getBases(); + } + + /** + * Update the value of scale_value to reflect the current value of + * scale_factor. + **/ + private void setScaleValue() + { + final int scale_factor = getScaleFactor(); + + if(scale_factor > 0) + scale_value =(float)(1 / Math.pow(3, scale_factor - 1)); + else + scale_value = getFontWidth(); + } + + /** + * Scroll and scale the display so that the given first base is at the left + * edge of the screen and the given last base is at the right edge. + **/ + public void setFirstAndLastBase(final int first, final int last) + { + left_edge_base = first; + setScaleValue(1.0F * getCanvasWidth() /(last - first + 1)); + } + + /** + * Scroll the display so that the given first base is at the left edge of + * the screen. + **/ + public void setFirstBase(int base_position) + { + if(base_position > getSequenceLength()) + base_position = getSequenceLength(); + + if(base_position < 1 && hard_left_edge) + base_position = 1; + + setFirstVisibleForwardBase(base_position); + fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT); + } + + /** + * Set the scale value to use for this FeatureDisplay. Note that it is + * better to call setScaleFactor() which will also set the scale value. + **/ + private void setScaleValue(final float scale_value) + { + if(scale_value <= 0) + throw new Error("internal error in FeatureDisplay.setScaleValue() - " + + "scale value must be positive"); + + this.scale_value = scale_value; + + if(scale_value > 1) + scale_factor = 1; + else + scale_factor = + (int) Math.round(Math.log(1/scale_value) / Math.log(3)) + 1; + + scale_changer.setValue(scale_factor); + + fixScrollbar(); + needVisibleFeatureVectorUpdate(); + getCanvas().repaint(); + fireAdjustmentEvent(DisplayAdjustmentEvent.SCALE_ADJUST_EVENT); + } + + /** + * Make a new Range and throw a Error is an OutOfRangeException occurs. + **/ + private Range newRange(final int start, final int end) + { + try + { + return new Range(start, end); + } + catch(OutOfRangeException e) + { + throw new Error("internal error - unexpected exception: " + e); + } + } + + /** + * Arrange for updateVisibleFeatureVector() to be called in the next call + * to paint() + **/ + private void needVisibleFeatureVectorUpdate() + { + update_visible_features = true; + } + +} diff --git a/uk/ac/sanger/artemis/components/FeatureEdit.java b/uk/ac/sanger/artemis/components/FeatureEdit.java new file mode 100644 index 000000000..bd4988755 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureEdit.java @@ -0,0 +1,1276 @@ +/* FeatureEdit.java + * + * created: Tue Dec 1 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000,2001,2002 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/FeatureEdit.java,v 1.1 2004-06-09 09:46:38 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.io.OutOfDateException; +import uk.ac.sanger.artemis.io.LocationParseException; +import uk.ac.sanger.artemis.io.InvalidRelationException; +import uk.ac.sanger.artemis.io.QualifierParseException; +import uk.ac.sanger.artemis.io.Range; +import uk.ac.sanger.artemis.io.RangeVector; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.KeyVector; +import uk.ac.sanger.artemis.io.Location; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.QualifierVector; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.io.StreamQualifier; +import uk.ac.sanger.artemis.io.QualifierInfo; +import uk.ac.sanger.artemis.io.EmblStreamFeature; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.Date; + +import javax.swing.*; + +/** + * FeatureEdit class + * + * @author Kim Rutherford + * @version $Id: FeatureEdit.java,v 1.1 2004-06-09 09:46:38 tjc Exp $ + **/ + +public class FeatureEdit + extends JFrame + implements EntryChangeListener, FeatureChangeListener { + /** + * Create a new FeatureEdit object from the given Feature. + * @param entry_group The EntryGroup that contains this Feature. + * @param selection The Selection operate on. The operations are "Remove + * Range" and "Grab Range" + * @param goto_event_source The object the we will call gotoBase () on. + **/ + public FeatureEdit (final Feature edit_feature, + final EntryGroup entry_group, + final Selection selection, + final GotoEventSource goto_event_source) { + super ("Artemis Feature Edit: " + edit_feature.getIDString () + + (edit_feature.isReadOnly () ? + " - (read only)" : + "")); + + this.edit_feature = edit_feature; + this.edit_entry = edit_feature.getEntry (); + this.entry_group = entry_group; + this.selection = selection; + this.goto_event_source = goto_event_source; + + createComponents (); + + updateFromFeature (); + + orig_qualifier_text = qualifier_text_area.getText (); + + edit_feature.getEntry ().addEntryChangeListener (this); + edit_feature.addFeatureChangeListener (this); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + stopListening (); + dispose (); + } + }); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + qualifier_text_area.requestFocus(); + } + + /** + * Remove this object as a feature and entry change listener. + **/ + public void stopListening () { + getEntry ().removeEntryChangeListener (this); + getFeature ().removeFeatureChangeListener (this); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so we can notify the user if of this component if the + * feature gets deleted. + **/ + public void entryChanged (EntryChangeEvent event) { + switch (event.getType ()) { + case EntryChangeEvent.FEATURE_DELETED: + if (event.getFeature () == edit_feature) { + stopListening (); + dispose (); + } + break; + default: + // do nothing; + break; + } + } + + /** + * Add an ActionListener to the Cancel JButton of this FeatureEdit. + **/ + public void addCancelActionListener (final ActionListener l) { + cancel_button.addActionListener (l); + } + + /** + * Remove an ActionListener from the Cancel JButton of this FeatureEdit. + **/ + public void removeCancelActionListener (final ActionListener l) { + cancel_button.removeActionListener (l); + } + + /** + * Add an ActionListener to the Apply JButton of this FeatureEdit. + **/ + public void addApplyActionListener (final ActionListener l) { + apply_button.addActionListener (l); + } + + /** + * Remove an ActionListener from the Apply JButton of this FeatureEdit. + **/ + public void removeApplyActionListener (final ActionListener l) { + apply_button.removeActionListener (l); + } + + /** + * Implementation of the FeatureChangeListener interface. We need to + * listen to feature change events from the Features in this object so that + * we can update the display. + * @param event The change event. + **/ + public void featureChanged (FeatureChangeEvent event) { + // re-read the information from the feature + switch (event.getType ()) { + case FeatureChangeEvent.LOCATION_CHANGED: + updateLocation (); + break; + case FeatureChangeEvent.KEY_CHANGED: + updateKey (); + break; + case FeatureChangeEvent.QUALIFIER_CHANGED: + if (qualifier_text_area.getText ().equals (orig_qualifier_text)) { + updateFromFeature (); + } else { + final String message = + "warning: the qualifiers have changed outside the editor - " + + "view now?"; + + final YesNoDialog yes_no_dialog = + new YesNoDialog (FeatureEdit.this, message); + + if (yes_no_dialog.getResult ()) { + new FeatureViewer (getFeature ()); + } + } + break; + default: + updateFromFeature (); + break; + } + } + + + /** + * Create all the components for this FeatureEdit component. + **/ + private void createComponents () { + qualifier_text_area = new QualifierTextArea (); + + qualifier_text_area.setWrapStyleWord (true); + + key_choice = + new KeyChoice (getEntryInformation (),getFeature ().getKey ()); + + final JPanel key_and_qualifier_panel = new JPanel (); + + location_text.setBackground (Color.white); + + final JPanel key_panel = new JPanel (); + key_panel.add (new JLabel ("Key:")); + key_panel.add (key_choice); + + key_and_qualifier_panel.setLayout (new BorderLayout ()); + key_and_qualifier_panel.add (key_panel, "West"); + + qualifier_choice = new QualifierChoice (getEntryInformation (), + key_choice.getSelectedItem (), + null); + + final JPanel qualifier_panel = new JPanel (); + + final JButton qualifier_add_button = new JButton ("Add Qualifier:"); + + qualifier_panel.add (qualifier_add_button); + qualifier_panel.add (qualifier_choice); + + key_and_qualifier_panel.add (qualifier_panel, "East"); + + key_choice.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + qualifier_choice.setKey (key_choice.getSelectedItem ()); + } + }); + + qualifier_add_button.addActionListener (new ActionListener () { + public void actionPerformed(ActionEvent e) { + final String qualifier_name = + (String) qualifier_choice.getSelectedItem (); + + QualifierInfo qualifier_info = + getEntryInformation ().getQualifierInfo (qualifier_name); + + if (qualifier_info == null) { + qualifier_info = + new QualifierInfo (qualifier_name, + QualifierInfo.OPTIONAL_QUOTED_TEXT, + null, null, false); + } + + qualifier_text_area.append ("/" + qualifier_name); + + switch (qualifier_info.getType ()) { + case QualifierInfo.QUOTED_TEXT: + if (qualifier_name.equals ("GO")) { + // special case for /GO + final java.util.Calendar calendar = + java.util.Calendar.getInstance (); + + final java.util.Date current_time = + calendar.getTime (); + + final java.text.SimpleDateFormat date_format = + new java.text.SimpleDateFormat("yyyyMMdd"); + + final StringBuffer result_buffer = new StringBuffer (); + + date_format.format(current_time, result_buffer, + new java.text.FieldPosition(java.text.DateFormat.DATE_FIELD)); + + final String go_string = + "aspect=; term=; GOid=GO:; "+ + "evidence=ISS; db_xref=GOC:unpublished; " + + "with=UNIPROT:; date=" + result_buffer; + qualifier_text_area.append ("=\"" + go_string + "\""); + } else { + qualifier_text_area.append ("=\"\""); + } + break; + + case QualifierInfo.NO_VALUE: + case QualifierInfo.OPTIONAL_QUOTED_TEXT: + break; + + default: + qualifier_text_area.append ("="); + } + + qualifier_text_area.append ("\n"); + } + }); + + final JPanel middle_panel = new JPanel (); + middle_panel.setLayout (new BorderLayout ()); + + final JPanel lower_panel = new JPanel (); + lower_panel.setLayout (new BorderLayout ()); + + final JPanel outer_location_button_panel = new JPanel (); + lower_panel.add (outer_location_button_panel, "North"); + outer_location_button_panel.setLayout (new BorderLayout ()); + + final JPanel location_button_panel = new JPanel (); + outer_location_button_panel.add (location_button_panel, "West"); + + final JPanel location_panel = new JPanel (); + location_panel.setLayout (new BorderLayout ()); + location_panel.add (new JLabel ("location: "), "West"); + location_panel.add (location_text, "Center"); + + final JButton complement_button = new JButton ("Complement"); + location_button_panel.add (complement_button); + complement_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + complementLocation (); + } + }); + + final JButton grab_button = new JButton ("Grab Range"); + location_button_panel.add (grab_button); + grab_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + grabSelectedRange (); + } + }); + + final JButton remove_button = new JButton ("Remove Range"); + location_button_panel.add (remove_button); + remove_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + removeSelectedRange (); + } + }); + + final JButton goto_button = new JButton ("Goto Feature"); + location_button_panel.add (goto_button); + goto_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + goto_event_source.gotoBase (getFeature ().getFirstBaseMarker ()); + } + }); + + final JButton select_button = new JButton ("Select Feature"); + location_button_panel.add (select_button); + select_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + getSelection ().set (getFeature ()); + } + }); + + final boolean sanger_options = + Options.getOptions ().getPropertyTruthValue ("sanger_options"); + + if (sanger_options) { + // a PSU only hack + final JButton tidy_button = new JButton ("Tidy"); + location_button_panel.add (tidy_button); + tidy_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + try { + tidy (); + } catch (QualifierParseException exception) { + final String error_string = exception.getMessage (); + new MessageDialog (FeatureEdit.this, + "Cannot tidy - qualifier error: " + + error_string); + } + } + }); + } + + if (Options.getOptions ().getProperty ("external_editor") != null) { + final JButton external_fasta_edit_button = new JButton ("MESS/FASTA"); + location_button_panel.add (external_fasta_edit_button); + external_fasta_edit_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + try { + if (getFeature ().getQualifierByName ("fasta_file") != null) { + final String DEFAULT_MAX_EUK_FASTA_HITS = "10"; + + final String max_fasta_hits; + + final String max_fasta_hits_from_options = + Options.getOptions ().getProperty ("mess_fasta_hits"); + + if (Options.getOptions ().isEukaryoticMode ()) { + if (max_fasta_hits_from_options == null) { + max_fasta_hits = DEFAULT_MAX_EUK_FASTA_HITS; + } else { + max_fasta_hits = max_fasta_hits_from_options; + } + + externalEdit (new String [] { + "-fasta", + "-maxhits", + max_fasta_hits, + "-euk" + }); + } else { + if (max_fasta_hits_from_options == null) { + externalEdit (new String [] { + "-fasta" + }); + } else { + externalEdit (new String [] { + "-fasta", + "-maxhits", + max_fasta_hits_from_options + }); + + } + } + return; + } + } catch (InvalidRelationException _) { + // fall through + } + new MessageDialog (FeatureEdit.this, + "nothing to edit - no /fasta_file qualifier"); + } + }); + + final JButton external_blastp_edit_button = new JButton ("MESS/BLASTP"); + location_button_panel.add (external_blastp_edit_button); + external_blastp_edit_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + try { + if (getFeature ().getQualifierByName ("blastp_file") != null) { + final String DEFAULT_MAX_BLASTP_HITS = "10"; + + final String max_blastp_hits; + + final String max_blastp_hits_from_options = + Options.getOptions ().getProperty ("mess_blastp_hits"); + + if (max_blastp_hits_from_options == null) { + max_blastp_hits = DEFAULT_MAX_BLASTP_HITS; + } else { + max_blastp_hits = max_blastp_hits_from_options; + } + + if (Options.getOptions ().isEukaryoticMode ()) { + externalEdit (new String [] { + "-blastp", + "-maxhits", + max_blastp_hits, + "-euk" + }); + } else { + externalEdit (new String [] { + "-blastp", + "-maxhits", + max_blastp_hits + }); + } + return; + } + } catch (InvalidRelationException _) { + // fall through + } + new MessageDialog (FeatureEdit.this, + "nothing to edit - no /blastp_file qualifier"); + } + }); + + final JButton external_go_edit_button = new JButton ("MESS/GO"); + location_button_panel.add (external_go_edit_button); + external_go_edit_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + try { + if (getFeature ().getQualifierByName ("blastp+go_file") != null) { + final String DEFAULT_MAX_GO_BLAST_HITS = "10"; + + final String max_go_blast_hits; + + final String max_go_blast_hits_from_options = + Options.getOptions ().getProperty ("mess_blast_go_hits"); + + if (max_go_blast_hits_from_options == null) { + max_go_blast_hits = DEFAULT_MAX_GO_BLAST_HITS; + } else { + max_go_blast_hits = max_go_blast_hits_from_options; + } + + if (Options.getOptions ().isEukaryoticMode ()) { + externalEdit (new String [] { + "-blastp+go", + "-maxhits", + max_go_blast_hits, + "-euk" + }); + } else { + externalEdit (new String [] { + "-blastp+go", + "-maxhits", + max_go_blast_hits + }); + } + return; + } + } catch (InvalidRelationException _) { + // fall through + } + new MessageDialog (FeatureEdit.this, + "nothing to edit - no /blastp+go_file qualifier"); + } + }); + } + + middle_panel.add (location_panel, "North"); + + getContentPane ().add (key_and_qualifier_panel, "North"); + + cancel_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + stopListening (); + dispose (); + } + }); + + if (!getFeature ().isReadOnly ()) { + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + if (setFeature ()) { + stopListening (); + dispose (); + } + } + }); + + apply_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + setFeature (); + } + }); + } + + final FlowLayout flow_layout = + new FlowLayout (FlowLayout.CENTER, 18, 5); + + final JPanel ok_cancel_update_panel = new JPanel (flow_layout); + + if (!getFeature ().isReadOnly ()) { + ok_cancel_update_panel.add (ok_button); + } + + ok_cancel_update_panel.add (cancel_button); + + if (!getFeature ().isReadOnly ()) { + ok_cancel_update_panel.add (apply_button); + } + + getContentPane ().add (ok_cancel_update_panel, "South"); + + middle_panel.add (lower_panel, "Center"); + lower_panel.add (new JScrollPane (qualifier_text_area), "Center"); + + getContentPane ().add (middle_panel, "Center"); + } + + /** + * Read the key, location and qualifier information from the feature and + * update the components. + **/ + private void updateFromFeature () { + datestamp = getFeature ().getDatestamp (); + + updateKey (); + + updateLocation (); + + updateQualifiers (); + } + + /** + * Read the location from the feature and update the location field. + **/ + private void updateLocation () { + location_text.setText (getFeature ().getLocation ().toStringShort ()); + } + + /** + * Complement the current location_text. + **/ + private void complementLocation () { + if (rationalizeLocation ()) { + if (location_text.getText ().startsWith ("complement(")) { + final String new_text = location_text.getText ().substring (11); + if (new_text.endsWith (")")) { + final String new_text2 = + new_text.substring (0, new_text.length () - 1); + location_text.setText (new_text2); + } else { + location_text.setText (new_text); + } + } else { + final String new_text = location_text.getText (); + + location_text.setText ("complement(" + new_text + ")"); + } + } else { + new MessageDialog (this, "complement failed - " + + "current location cannot be parsed"); + } + } + + /** + * Tidy the qualifiers by removing any indication that the annotation has + * been transferred from another gene. Remove the "transferred_" part of + * all qualifier names and the "[[FROM sc_001234 abc1]]" bit of each + * corresponding qualifier value. + **/ + private void tidy () + throws QualifierParseException { + final StringBuffer buffer = new StringBuffer (); + + final QualifierVector qualifiers = + qualifier_text_area.getParsedQualifiers (getEntryInformation ()); + + for (int qualifier_index = 0 ; + qualifier_index < qualifiers.size () ; + ++qualifier_index) { + final Qualifier this_qualifier = qualifiers.elementAt (qualifier_index); + + final QualifierInfo qualifier_info = + getEntryInformation ().getQualifierInfo (this_qualifier.getName ()); + + final StringVector qualifier_strings = + StreamQualifier.toStringVector (qualifier_info, this_qualifier); + + for (int value_index = 0 ; + value_index < qualifier_strings.size () ; + ++value_index) { + + final String qualifier_string = + qualifier_strings.elementAt (value_index); + + buffer.append (tidyHelper (qualifier_string) + "\n"); + } + } + + qualifier_text_area.setText (buffer.toString ()); + } + + /** + * Perform the action of tidy() on the String version of one qualifier. + **/ + private String tidyHelper (final String qualifier_string) { + final String temp_string; + + if (qualifier_string.startsWith ("/transferred_")) { + temp_string = "/" + qualifier_string.substring (13); + } else { + temp_string = qualifier_string; + } + + final int start_index = temp_string.indexOf ("<<FROM "); + final int end_index = temp_string.indexOf (">>"); + + if (start_index != -1 && end_index != -1) { + if (temp_string.length () > end_index + 2 && + temp_string.charAt (end_index + 2) == ' ') { + return temp_string.substring (0, start_index) + + temp_string.substring (end_index + 3); + } else { + return temp_string.substring (0, start_index) + + temp_string.substring (end_index + 2); + } + } else { + return temp_string; + } + } + + /** + * Add the currently selected range to location_text. + **/ + private void grabSelectedRange () { + if (!rationalizeLocation ()) { + new MessageDialog (this, + "grab failed - current location cannot be parsed"); + return; + } + + final Range selected_range = getSelection ().getSelectionRange (); + + if (selected_range == null) { + new MessageDialog (this, "grab failed - nothing is selected"); + return; + } + + // save it in case it gets mangled + final String old_location_text = location_text.getText (); + + if (old_location_text.endsWith ("))")) { + final String new_text = + old_location_text.substring (0, old_location_text.length () - 2); + + location_text.setText (new_text + "," + selected_range.getStart () + + ".." + selected_range.getEnd () + "))"); + } else { + if (old_location_text.endsWith (")")) { + final String new_text = + old_location_text.substring (0, old_location_text.length () - 1); + + location_text.setText (new_text + "," + selected_range.getStart () + + ".." + selected_range.getEnd () + ")"); + } else { + location_text.setText (old_location_text + "," + + selected_range.getStart () + + ".." + selected_range.getEnd ()); + } + } + + if (!rationalizeLocation ()) { + location_text.setText (old_location_text); + new MessageDialog (this, + "grab failed - location cannot be parsed after " + + "grabbing"); + } + } + + /** + * Remove the currently selected range of bases from location_text. + **/ + private void removeSelectedRange () { + if (!rationalizeLocation ()) { + new MessageDialog (this, + "grab failed - current location cannot be parsed"); + return; + } + + final MarkerRange selected_marker_range = + getSelection ().getMarkerRange (); + + if (selected_marker_range == null) { + new MessageDialog (this, "remove range failed - no bases are selected"); + return; + } + + final Range selected_range = selected_marker_range.getRawRange (); + + if (selected_marker_range.getStrand () != getFeature ().getStrand ()) { + new MessageDialog (this, "remove range failed - you need to select " + + "some bases on the other strand"); + return; + } + + final Location location; + + try { + location = new Location (location_text.getText ()); + } catch (LocationParseException e) { + // this shouldn't happen because we called rationalizeLocation () + throw new Error ("internal error - unexpected exception: " + e); + } + + final Range location_total_range = location.getTotalRange (); + + if (!selected_range.overlaps (location_total_range)) { + new MessageDialog (this, "remove range failed - the range you " + + "selected does not overlap the feature"); + return; + } + + if (selected_range.contains (location_total_range)) { + new MessageDialog (this, "remove range failed - the range you " + + "selected overlaps the whole feature"); + return; + } + + final RangeVector location_ranges = location.getRanges (); + final boolean location_is_complemented = location.isComplement (); + + final RangeVector new_ranges = new RangeVector (); + + // if the selected_range completely covers a range remove the + // range. otherwise if the selected_range is completely within one of the + // ranges two new ranges are created. if the selected_range is not + // completely contained then the appropriate end of the range is truncated + for (int i = 0 ; i < location_ranges.size () ; ++i) { + final Range this_range = location_ranges.elementAt (i); + + if (selected_range.overlaps (this_range)) { + try { + if (this_range.contains (selected_range) && + this_range.getStart () != selected_range.getStart () && + this_range.getEnd () != selected_range.getEnd ()) { + // chop a piece out of the middle and make two new ranges + final Range new_start_range = + this_range.change (selected_range.getEnd () + 1, + this_range.getEnd ()); + new_ranges.add (new_start_range); + final Range new_end_range = + this_range.change (this_range.getStart (), + selected_range.getStart () - 1); + new_ranges.add (new_end_range); + } else { + if (selected_range.contains (this_range)) { + // delete (ie. don't copy) the range + } else { + if (this_range.getStart () < selected_range.getStart ()) { + // truncate the end of the range + final Range new_start_range = + this_range.change (this_range.getStart (), + selected_range.getStart () - 1); + new_ranges.add (new_start_range); + } else { + if (this_range.getEnd () > selected_range.getEnd ()) { + // truncate the start of the range + final Range new_end_range = + this_range.change (selected_range.getEnd () + 1, + this_range.getEnd ()); + new_ranges.add (new_end_range); + + } else { + throw new Error ("internal error - can't remove range"); + } + } + } + } + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } else { + // copy it unchanged + new_ranges.add (this_range); + } + } + + final Location new_location = + new Location (new_ranges, location_is_complemented); + + location_text.setText (new_location.toStringShort ()); + } + + /** + * Attempt to parse the current location_text as a Location. If it can be + * parsed it will be canonicalized (ie. the complement, if any, will be + * outermost). Returns true if and only if the location_text could be + * parsed. + **/ + private boolean rationalizeLocation () { + try { + final Location location = new Location (location_text.getText ()); + + location_text.setText (location.toStringShort ()); + + return true; + } catch (LocationParseException e) { + return false; + } + } + + /** + * Used for getting the current time/date in externalEdit (). + **/ + private static java.util.Calendar calendar = + java.util.Calendar.getInstance (); + + /** + * Edit the qualifiers of this Feature in an external editor. The + * qualifiers will be set when the editor finishes. This method works by + * writing the qualifiers to a temporary file and the sequence of the + * feature to a different file. + * @param editor_extra_args Extra arguments to pass to the editor. null + * means there are no extra args. + **/ + private void externalEdit (final String [] editor_extra_args) { + try { + final String pre_edit_text = qualifier_text_area.getText (); + + // write to a temporary file + final java.util.Date current_time = calendar.getTime (); + + final String temp_file_name = + "/tmp/artemis_temp." + current_time.getTime (); + + final File temp_file = new File (temp_file_name); + + final FileWriter out_writer = new FileWriter (temp_file); + final PrintWriter print_writer = new PrintWriter (out_writer); + + print_writer.write (qualifier_text_area.getText ()); + + print_writer.close (); + out_writer.close (); + + + final File sequence_temp_file = new File (temp_file_name + ".seq"); + + final FileWriter sequence_out_writer = + new FileWriter (sequence_temp_file); + final PrintWriter sequence_print_writer = + new PrintWriter (sequence_out_writer); + + getFeature ().writeBasesOfFeature (sequence_print_writer); + + sequence_print_writer.close (); + sequence_out_writer.close (); + + + final String editor_path = + Options.getOptions ().getProperty ("external_editor"); + + final String [] process_args; + + if (editor_extra_args == null) { + process_args = new String [1]; + process_args[0] = temp_file.getCanonicalPath (); + } else { + process_args = new String [editor_extra_args.length + 1]; + System.arraycopy (editor_extra_args, 0, process_args, 0, + editor_extra_args.length); + process_args[process_args.length - 1] = temp_file.getCanonicalPath (); + } + + + final Process process = + ExternalProgram.startProgram (editor_path, process_args); + + final ProcessWatcher process_watcher = + new ProcessWatcher (process, "editor", false); + + final Thread watcher_thread = new Thread (process_watcher); + + watcher_thread.start (); + + final ProcessWatcherListener listener = + new ProcessWatcherListener () { + public void processFinished (final ProcessWatcherEvent event) { + try { + final FileReader file_reader = new FileReader (temp_file); + + final BufferedReader buffered_reader = + new BufferedReader (file_reader); + + final StringBuffer buffer = new StringBuffer (); + + while (true) { + final String line = buffered_reader.readLine (); + + if (line == null) { + final String current_qualifier_text = + qualifier_text_area.getText (); + + if (!current_qualifier_text.equals (pre_edit_text)) { + final String message = + "the qualifiers have changed - apply changes from the " + + "external editor?"; + + final YesNoDialog yes_no_dialog = + new YesNoDialog (FeatureEdit.this, message); + + if (!yes_no_dialog.getResult ()) { + return; + } + } + + qualifier_text_area.setText (buffer.toString ()); + + temp_file.delete (); + sequence_temp_file.delete (); + + return; + } else { + buffer.append (line + "\n"); + } + } + } catch (IOException e) { + new MessageDialog (FeatureEdit.this, "an error occured while " + + "reading from the editor: " + e); + } + } + }; + + process_watcher.addProcessWatcherListener (listener); + } catch (IOException e) { + new MessageDialog (this, "error while starting editor: " + e); + } catch (ExternalProgramException e) { + new MessageDialog (this, "error while starting editor: " + e); + } + } + + /** + * Read the qualifiers from the feature and update the qualifier JTextArea. + **/ + private void updateQualifiers () { + qualifier_text_area.setText (getQualifierString ()); + } + + /** + * Return a string containing one qualifier per line. These are the + * original qualifiers, not the qualifiers from the qualifier_text_area. + **/ + private String getQualifierString () { + final StringBuffer buffer = new StringBuffer (); + + final QualifierVector qualifiers = getFeature ().getQualifiers (); + + for (int qualifier_index = 0 ; + qualifier_index < qualifiers.size () ; + ++qualifier_index) { + final Qualifier this_qualifier = qualifiers.elementAt (qualifier_index); + + final QualifierInfo qualifier_info = + getEntryInformation ().getQualifierInfo (this_qualifier.getName ()); + + final StringVector qualifier_strings = + StreamQualifier.toStringVector (qualifier_info, this_qualifier); + + for (int value_index = 0 ; + value_index < qualifier_strings.size () ; + ++value_index) { + + final String qualifier_string = + qualifier_strings.elementAt (value_index); + + buffer.append (qualifier_string + "\n"); + } + } + + return buffer.toString (); + } + + /** + * Set the key, location and qualifiers of the feature to be the same as + * what values currently shown in the components. + * @return true if and only if action succeeds. It may fail because of an + * illegal location or qualifier, in which case a message will be + * displayed before returning. + **/ + private boolean setFeature () { + final Key key = key_choice.getSelectedItem (); + + final KeyVector possible_keys = getEntryInformation ().getValidKeys (); + + if (possible_keys != null && !possible_keys.contains (key)) { + final YesNoDialog dialog = + new YesNoDialog (this, "Add this new key: " + key + "?"); + + if (dialog.getResult ()) { + // yes + getEntryInformation ().addKey (key); + } else { + // no + return false; + } + } + + final Location location; + + try { + location = new Location (location_text.getText ()); + } catch (LocationParseException exception) { + final String error_string = exception.getMessage (); + System.out.println (error_string); + new MessageDialog (this, + "Cannot apply changes because of location error: " + + error_string); + + return false; + } + + + final QualifierVector qualifiers; + + try { + qualifiers = + qualifier_text_area.getParsedQualifiers (getEntryInformation ()); + } catch (QualifierParseException exception) { + final String error_string = exception.getMessage (); + System.out.println (error_string); + new MessageDialog (this, + "Cannot apply changes because of a qualifier " + + "error: " + error_string); + + return false; + } + + + try { + entry_group.getActionController ().startAction (); + + try { + getFeature ().set (datestamp, key, location, qualifiers); + } catch (OutOfDateException e) { + final YesNoDialog dialog = + new YesNoDialog (this, + "the feature has changed since the edit " + + "window was opened, continue?"); + if (dialog.getResult ()) { + // yes - ignore the datestamp + getFeature ().set (key, location, qualifiers); + } else { + // no + return false; + } + } + } catch (EntryInformationException e) { + final String error_string = e.getMessage (); + new MessageDialog (this, "Cannot apply changes: " + error_string); + + return false; + } catch (OutOfRangeException e) { + new MessageDialog (this, + "Cannot apply changes - the location is out of " + + "range for this sequence"); + return false; + } catch (ReadOnlyException e) { + new MessageDialog (this, + "Cannot apply changes - the feature is " + + "read only"); + return false; + } finally { + entry_group.getActionController ().endAction (); + } + + dribble (); + + return true; + } + + /** + * Return the Feature we are editing as passed to the constructor. + **/ + public Feature getFeature () { + return edit_feature; + } + + /** + * On Unix machines this method will append the text of the feature to a + * file in a current directory called .dribble + <the entry name> + **/ + private void dribble () { + if (!Options.isUnixHost ()) { + return; + } + + final String dribble_file_name; + + if (getEntry ().getName () != null) { + dribble_file_name = ".dribble." + getEntry ().getName (); + } else { + dribble_file_name = ".dribble.no_name"; + } + + try { + final Writer writer = new FileWriter (dribble_file_name, true); + getFeature ().writeNative (writer); + writer.flush (); + writer.close (); + } catch (IOException e) { + System.err.println ("IO exception while accessing " + dribble_file_name + + ": " + e.getMessage ()); + } + } + + /** + * Return the Entry that contains the Feature this object is displaying. + **/ + private Entry getEntry () { + return edit_entry; + } + + /** + * Read the key from the feature and update the key chooser. + **/ + private void updateKey () { + final Key feature_key = getFeature ().getKey (); + + key_choice.setKey (feature_key); + } + + /** + * Return the EntryInformation object of the entry containing the feature. + **/ + public EntryInformation getEntryInformation () { + return getEntry ().getEntryInformation (); + } + + /** + * Return the Selection that was passed to the constructor. + **/ + private Selection getSelection () { + return selection; + } + + /** + * The choice of feature keys - created in createComponents(). + **/ + private KeyChoice key_choice; + + /** + * The choice of qualifiers - created in createComponents(). + **/ + private QualifierChoice qualifier_choice = null; + + private final static int LOCATION_TEXT_WIDTH = 80; + + /** + * The location text - set by updateLocation(). + **/ + private JTextField location_text = new JTextField (LOCATION_TEXT_WIDTH); + + private JButton add_qualifier_button = new JButton ("Add qualifier"); + + /** + * When pressed - apply changes and dispose of the component. + **/ + private JButton ok_button = new JButton ("OK"); + + /** + * When pressed - discard changes and dispose of the component. + **/ + private JButton cancel_button = new JButton ("Cancel"); + + /** + * When pressed - apply changes and keep the component open. + **/ + private JButton apply_button = new JButton ("Apply"); + + /** + * Edit area for qualifiers - created by createComponents(). + **/ + private QualifierTextArea qualifier_text_area; + + /** + * The Feature this object is displaying. + **/ + private Feature edit_feature; + + /** + * The GotoEventSource that was passed to the constructor - used for the + * "Goto Feature" button. + **/ + private GotoEventSource goto_event_source; + + /** + * The Entry that contains the Feature this object is displaying. + **/ + private Entry edit_entry; + + /** + * The EntryGroup that contains this Feature (passed to the constructor). + **/ + private EntryGroup entry_group; + + /** + * The datestamp of the RWCorbaFeature when updateFromFeature () was last + * called or null if this is not a RWCorbaFeature. + **/ + private Date datestamp = null; + + /** + * The Selection that was passed to the constructor. + **/ + private Selection selection; + + /** + * The contents of the QualifierTextArea before the user edits anything. + * This is used to work out if anything has changed since the creation of + * the FeatureEdit. + **/ + final String orig_qualifier_text; +} diff --git a/uk/ac/sanger/artemis/components/FeatureInfo.java b/uk/ac/sanger/artemis/components/FeatureInfo.java new file mode 100644 index 000000000..3d8e12574 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureInfo.java @@ -0,0 +1,658 @@ +/* FeatureInfo.java + * + * created: Sat Dec 19 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000,2002 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/FeatureInfo.java,v 1.1 2004-06-09 09:46:39 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.plot.*; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This component displays a summary of the statistical information about one + * feature (such as correlation score, codon usage and gc content). + * + * @author Kim Rutherford + * @version $Id: FeatureInfo.java,v 1.1 2004-06-09 09:46:39 tjc Exp $ + **/ +public class FeatureInfo extends JFrame + implements EntryChangeListener, FeatureChangeListener { + /** + * Create a new FeatureInfo component. + * @param feature The Feature that this component is showing information + * about. + **/ + public FeatureInfo (final Feature feature, + final CodonUsageAlgorithm codon_usage_algorithm) { + super ("Feature infomation: " + feature.getIDString ()); + + this.feature = feature; + this.entry = feature.getEntry (); + this.codon_usage_algorithm = codon_usage_algorithm; + + setBackground (new Color (210, 210, 210)); + getContentPane ().setBackground (new Color (210, 210, 210)); + + codon_info_areas = new JTextArea [4][4]; + base_count_info_areas = new JTextArea [4]; + + final JPanel centre_panel = new JPanel (); + centre_panel.setLayout (new BorderLayout ()); + + makeCountList (); + getContentPane ().add (aa_count_panel, "West"); + + makeMiscInfogrid (); + centre_panel.add (misc_info_panel, "South"); + + makeCodonInfogrid (); + centre_panel.add (codon_info_panel, "Center"); + + getContentPane ().add (centre_panel, "Center"); + + button_panel = new JPanel (); + final JButton close_button = new JButton ("Close"); + + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + stopListening (); + FeatureInfo.this.dispose (); + } + }); + + button_panel.add (close_button); + + getContentPane ().add (button_panel, "South"); + + updateComponents (); + + getFeature ().addFeatureChangeListener (this); + getFeature ().getEntry ().addEntryChangeListener (this); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + stopListening (); + FeatureInfo.this.dispose (); + } + }); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + setVisible (true); + } + + /** + * Remove this object as a feature change and entry change listener. + **/ + private void stopListening () { + getEntry ().removeEntryChangeListener (this); + getFeature ().removeFeatureChangeListener (this); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so we can notify the user if of this component if the + * feature gets deleted. + **/ + public void entryChanged (EntryChangeEvent event) { + switch (event.getType ()) { + case EntryChangeEvent.FEATURE_DELETED: + if (event.getFeature () == getFeature ()) { + stopListening (); + dispose (); + } + break; + default: + // do nothing; + break; + } + } + + /** + * Implementation of the FeatureChangeListener interface. We need to + * listen to feature change events from the Features in this object. + * @param event The change event. + **/ + public void featureChanged (FeatureChangeEvent event) { + updateComponents (); + } + + /** + * Create all the labels in the misc_info_panel. + **/ + private void makeMiscInfogrid () { + misc_info_panel = new JPanel (); + + GridLayout grid = new GridLayout (0, 1); + misc_info_panel.setLayout (grid); + + molecular_weight_label = new JLabel (); + misc_info_panel.add (molecular_weight_label); + + correlation_scores_label = new JLabel (); + misc_info_panel.add (correlation_scores_label); + + if (codon_usage_algorithm != null) { + usage_scores_label = new JLabel (); + misc_info_panel.add (usage_scores_label); + } + } + + /** + * Create a List containing a count of the occurrences of each codon in the + * feature. + **/ + private void makeCountList () { + aa_count_panel = new JPanel (); + + final JPanel aa_count_sub_panel1 = new JPanel (); + aa_count_sub_panel1.setLayout (new GridLayout (0, 1)); + aa_count_panel.add (aa_count_sub_panel1); + + final JPanel aa_count_sub_panel2 = new JPanel (); + aa_count_sub_panel2.setLayout (new GridLayout (0, 1)); + aa_count_panel.add (aa_count_sub_panel2); + + aa_count_list = new JLabel [AminoAcidSequence.symbol_count]; + + for (int i = 0 ; i < AminoAcidSequence.symbol_count / 2 ; ++i) { + aa_count_list[i] = new JLabel (); + aa_count_sub_panel1.add (aa_count_list[i]); + } + + // add a blank label so that the columns balance + aa_count_sub_panel1.add (new JLabel ("")); + + for (int i = AminoAcidSequence.symbol_count / 2 ; + i < AminoAcidSequence.symbol_count ; + ++i) { + aa_count_list[i] = new JLabel (); + aa_count_sub_panel2.add (aa_count_list[i]); + } + } + + /** + * Create all the labels in the codon_info_panel. + **/ + private void makeCodonInfogrid () { + codon_info_panel = new JPanel (); + + GridBagLayout gridbag = new GridBagLayout(); + + codon_info_panel.setLayout (gridbag); + + GridBagConstraints c = new GridBagConstraints(); + + final int GRID_WIDTH = 7; + + c.gridwidth = GRID_WIDTH; + c.anchor = GridBagConstraints.NORTH; + c.insets = new Insets (1, 1, 1, 1); + c.fill = GridBagConstraints.NONE; + + + // create the first row with some dummy labels to pad it out + + // the first row has the bases T,C,A,G as column titles + final JLabel dummy_label1 = new JLabel (""); + gridbag.setConstraints (dummy_label1, c); + codon_info_panel.add (dummy_label1); + + final JLabel dummy_label2 = new JLabel (""); + gridbag.setConstraints (dummy_label2, c); + codon_info_panel.add (dummy_label2); + + for (int i = 0 ; i < Bases.letter_index.length ; ++i) { + final JLabel new_label = + new JLabel (String.valueOf (Bases.letter_index[i]).toUpperCase ()); + gridbag.setConstraints (new_label, c); + codon_info_panel.add (new_label); + } + + final JLabel dummy_label3 = new JLabel (""); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (dummy_label3, c); + codon_info_panel.add (dummy_label3); + + c.anchor = GridBagConstraints.WEST; + c.gridwidth = GRID_WIDTH; + + final int ROWS = 4; + + // create the main rows + + for (int row = 0 ; row < ROWS ; ++row) { + { + final JLabel new_label = + new JLabel (String.valueOf (Bases.letter_index[row]).toUpperCase ()); + + new_label.setBorder (new javax.swing.border.EmptyBorder (1,1,1,1)); + gridbag.setConstraints (new_label, c); + codon_info_panel.add (new_label); + } + + { + base_count_info_areas[row] = new JTextArea (5, 15); + base_count_info_areas[row].setEditable (false); + + final JScrollPane scroll_pane = + new JScrollPane (base_count_info_areas[row]); + gridbag.setConstraints (scroll_pane, c); + codon_info_panel.add (scroll_pane); + + final Color parent_background = getBackground (); + base_count_info_areas[row].setBackground (parent_background); + } + + for (int column = 2 ; column < GRID_WIDTH - 1 ; ++column) { + + final JTextArea new_text = new JTextArea (5, 12); + new_text.setText ("r: " + row + " c: " + column); + + new_text.setEditable (false); + + new_text.setBackground (Color.white); + + final JScrollPane scroll_pane = new JScrollPane (new_text); + gridbag.setConstraints (scroll_pane, c); + codon_info_panel.add (scroll_pane); + + codon_info_areas[row][column - 2] = new_text; + } + + c.gridwidth = GridBagConstraints.REMAINDER; + + { + final JTextArea new_text = new JTextArea (5, 2); + new_text.setText (Bases.letter_index[0] + "\n" + + Bases.letter_index[1] + "\n" + + Bases.letter_index[2] + "\n" + + Bases.letter_index[3]); + + new_text.setEditable (false); + + final JScrollPane scroll_pane = new JScrollPane (new_text); + gridbag.setConstraints (scroll_pane, c); + codon_info_panel.add (scroll_pane); + } + + c.gridwidth = GRID_WIDTH; + } + } + + /** + * Set the List components with the information from the feature. + **/ + private void updateComponents () { + updateAACountList (); + + updateCodonInfoAreas (); + + updateMolecularWeightLabel (); + + updateCorrelationScoresLabel (); + + updateUsageScoresLabel (); + + updateBaseCounts (); + + validate (); + } + + /** + * Update the aa_count_list JTextArea. + **/ + private void updateAACountList () { + for (int i = 0 ; i < AminoAcidSequence.symbol_count ; ++i) { + if (i == AminoAcidSequence.symbol_count - 1 && + getFeature ().getResidueCount (i) == 0) { + // don't include Selenocysteine in the list in the list if there are + // none + aa_count_list[i].setText (""); + continue; + } + + final String three_letter_abbreviation = + AminoAcidSequence.getThreeLetterAbbreviation (i); + + final char one_letter_abbreviation = + Character.toUpperCase (AminoAcidSequence.getSymbolFromIndex (i)) ; + + final String label_string = + three_letter_abbreviation + " (" + one_letter_abbreviation +"): " + + getFeature ().getResidueCount (i); + + aa_count_list[i].setText (label_string); + } + } + + /** + * Update all the JTextArea objects in codon_info_areas. + **/ + private void updateCodonInfoAreas () { + for (int first = 0 ; first < 4 ; ++first) { + + for (int second = 0 ; second < 4 ; ++second) { + + codon_info_areas[first][second].setText (""); + + for (int third = 0 ; third < 4 ; ++third) { + + final char this_codon_symbol = + AminoAcidSequence.getCodonTranslation (Bases.letter_index[first], + Bases.letter_index[second], + Bases.letter_index[third]); + + final String this_codon_string = + AminoAcidSequence.getThreeLetterAbbreviation (this_codon_symbol); + + final int this_codon_count = + getFeature ().getCodonCount (first,second,third); + + final int this_codon_index = + AminoAcidSequence.getSymbolIndex (this_codon_symbol); + + final int this_amino_acid_count = + getFeature ().getResidueCount (this_codon_index); + + final String percent_string; + + if (this_amino_acid_count == this_codon_count) { + percent_string = "ALL"; + } else { + if (this_amino_acid_count < this_codon_count) { + // some of the amino acids aren't coded for by the standard + // codon - probably because of the /transl_except qualifier + percent_string = "---"; + } else { + final int percentage = + 100 * this_codon_count / this_amino_acid_count; + + percent_string = String.valueOf (percentage) + "%"; + } + } + + final String first_part_of_line = + this_codon_string + " " + this_codon_count; + + codon_info_areas[first][second].append (first_part_of_line); + + final int line_length_without_spaces = + first_part_of_line.length () + percent_string.length (); + + // add some spaces so that everything lines up nicely + for (int i = 0 ; i < 11 - line_length_without_spaces ; ++i) { + codon_info_areas[first][second].append (" "); + } + + codon_info_areas[first][second].append (percent_string); + + if (third != 3) { + // insert a newline on all but the last line + codon_info_areas[first][second].append ("\n"); + } + } + } + } + } + + /** + * Update molecular_weight_label. + **/ + private void updateMolecularWeightLabel () { + final String new_text = + "Mol weight: " + + getFeature ().getTranslation ().getMolecularWeight () + " Start: " + + getFeature ().getFirstBase () + " End: " + + getFeature ().getLastBase () + " Bases: " + + getFeature ().getBaseCount () + " AA length: " + + getFeature ().getTranslation ().length (); + molecular_weight_label.setText (new_text); + } + + + /** + * The method updates the components in base_count_info_areas. + **/ + private void updateBaseCounts () { + final int translation_base_count = + getFeature ().getTranslationBases ().length (); + + final int amino_acid_count = getFeature ().getTranslation ().length (); + + for (int i = 0 ; i < 4 ; ++i) { + base_count_info_areas[i].setText (""); + { + final String string = + "ALL:" + updateBaseCountsFormatter (getFeature ().getBaseCount (i), + translation_base_count); + + base_count_info_areas[i].append (string); + } + + for (int codon_base_index = 0; + codon_base_index < 3 ; + ++codon_base_index) { + String label = null; + switch (codon_base_index) { + case 0: label = "\n1st:"; break; + case 1: label = "\n2nd:"; break; + case 2: label = "\n3rd:"; break; + } + + final int base_count = + getFeature ().getPositionalBaseCount (codon_base_index, i); + + final String string = + label + updateBaseCountsFormatter (base_count, amino_acid_count); + + base_count_info_areas[i].append (string); + } + } + } + + /** + * A helper method for updateBaseCounts (). + **/ + private String updateBaseCountsFormatter (int base_count, + int total) { + final String count_string = " " + base_count; + + final String percent_string; + + if (base_count < total) { + percent_string = " " + 100 * base_count / total + "%"; + } else { + percent_string = "ALL"; + } + + final int count_width = 5; + + final int percent_width = 3; + + return + count_string.substring (count_string.length () - count_width) + " " + + percent_string.substring (percent_string.length () - percent_width); + } + + /** + * Update correlation_scores_label. + **/ + private void updateCorrelationScoresLabel () { + final int c_total = + getFeature ().getBaseCount (Bases.getIndexOfBase ('c')); + final int g_total = + getFeature ().getBaseCount (Bases.getIndexOfBase ('g')); + + final String c3_score; + final String g1_score; + final String g3_score; + + final int c3_count = + getFeature ().getPositionalBaseCount (2, Bases.getIndexOfBase ('c')); + final int g1_count = + getFeature ().getPositionalBaseCount (0, Bases.getIndexOfBase ('g')); + final int g3_count = + getFeature ().getPositionalBaseCount (2, Bases.getIndexOfBase ('g')); + + if (c_total == 0) { + c3_score = "ALL"; + } else { + c3_score = + String.valueOf (1000 * (3 * c3_count - c_total) / c_total / 10.0); + } + + if (g_total == 0) { + g1_score = "ALL"; + } else { + g1_score = + String.valueOf (1000 * (3 * g1_count - g_total) / g_total / 10.0); + } + + if (g_total == 0) { + g3_score = "ALL"; + } else { + g3_score = + String.valueOf (1000 * (3 * g3_count - g_total) / g_total / 10.0); + } + + final double cor1_2_score = + ((int) getFeature ().get12CorrelationScore () * 10) / 10.0; + + final double gc_percent = + ((int) (getFeature ().getPercentGC () * 100.0)) / 100.0; + + final String new_label = + "position 1/2 score = " + cor1_2_score + " " + + "C3/G1/G3 (o-e)/e = " + c3_score + " " + g1_score + " " + g3_score + + " " + gc_percent + "% GC"; + + correlation_scores_label.setText (new_label); + } + + /** + * Update usage_scores_label. + **/ + private void updateUsageScoresLabel () { + if (codon_usage_algorithm != null) { + final String new_label = + "usage score = " + codon_usage_algorithm.getFeatureScore (feature); + + usage_scores_label.setText (new_label); + } + } + + /** + * Return the feature this component is showing information about. + **/ + private Feature getFeature () { + return feature; + } + + /** + * Return the Entry that contains the Feature this object is displaying. + **/ + private Entry getEntry () { + return entry; + } + + /** + * The Feature that this component is showing information about. + **/ + private Feature feature = null; + + /** + * The Entry that contains the Feature this object is displaying. + **/ + private Entry entry; + + /** + * A panel containing the labels with the amino acid counts for the + * feature. + **/ + private JPanel aa_count_panel = null; + + /** + * An array Labels of showing the amino acid counts for the feature. There + * is one Label for each symbol (24). + **/ + private JLabel [] aa_count_list = null; + + /** + * A panel containing general statistics about the whole sequence. + **/ + private JPanel misc_info_panel = null; + + /** + * A Label containing molecular weight of the protein, start and end + * positions and length. + **/ + private JLabel molecular_weight_label = null; + + /** + * A Label containing the correlation scores for this feature. + **/ + private JLabel correlation_scores_label = null; + + /** + * A Label containing the usage scores for this feature. + **/ + private JLabel usage_scores_label = null; + + /** + * Statistics about the codon frequency. + **/ + private JPanel codon_info_panel = null; + + /** + * A panel to hold the close button. + **/ + private JPanel button_panel = null; + + /** + * These components each hold information about 4 codons each. + **/ + private JTextArea [] [] codon_info_areas; + + /** + * These components display position information about the base counts. + * See positional_base_counts and base_counts. + **/ + private JTextArea [] base_count_info_areas; + + /** + * The CodonUsageAlgorithm reference that was passed to the constructor. + * (Used by updateUsageScoresLabel ()). + **/ + private final CodonUsageAlgorithm codon_usage_algorithm; +} diff --git a/uk/ac/sanger/artemis/components/FeatureList.java b/uk/ac/sanger/artemis/components/FeatureList.java new file mode 100644 index 000000000..435800716 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureList.java @@ -0,0 +1,971 @@ +/* FeatureList.java + * + * created: Fri Oct 9 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000,2001,2002 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/FeatureList.java,v 1.1 2004-06-09 09:46:40 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.plot.*; + +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.InvalidRelationException; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.QualifierInfo; +import uk.ac.sanger.artemis.io.QualifierVector; +import uk.ac.sanger.artemis.io.StreamQualifier; +import uk.ac.sanger.artemis.util.StringVector; + +import java.awt.event.*; +import java.awt.*; +import java.text.*; +import java.util.*; + +import javax.swing.*; + +/** + * This component gives the user a list containing the details the current + * Features. + * + * @author Kim Rutherford + * @version $Id: FeatureList.java,v 1.1 2004-06-09 09:46:40 tjc Exp $ + * + **/ + +public class FeatureList extends EntryGroupPanel + implements EntryGroupChangeListener, + EntryChangeListener, FeatureChangeListener, + SelectionChangeListener, DisplayComponent +{ + /** + * Create a new FeatureList with the default number of rows. + * @param entry_group The EntryGroup that this component will display. + * @param selection The Selection object for this component. Selected + * objects will be highlighted. + * @param goto_event_source The object to use when we need to call + * gotoBase (). + **/ + public FeatureList (final EntryGroup entry_group, + final Selection selection, + final GotoEventSource goto_event_source, + final BasePlotGroup base_plot_group) { + super (entry_group, selection, goto_event_source, base_plot_group); + + getCanvas ().addMouseListener (new MouseAdapter () { + /** + * Listen for mouse press events so that we can do popup menus and + * selection. + **/ + public void mousePressed (MouseEvent event) { + if (isMenuTrigger (event)) { + final FeaturePopup popup = + new FeaturePopup (FeatureList.this, + getEntryGroup (), + getSelection (), + getGotoEventSource (), + getBasePlotGroup ()); + final JComponent parent = (JComponent) event.getSource (); + + popup.show (parent, event.getX (), event.getY ()); + } else { + handleCanvasMousePress (event); + } + } + }); + + createScrollbars (); + + addComponentListener (new ComponentAdapter () { + public void componentShown (ComponentEvent e) { + repaintCanvas (); + } + public void componentResized (ComponentEvent e) { + repaintCanvas (); + } + }); + + getSelection ().addSelectionChangeListener (this); + + // changes to the EntryGroup will be noticed by listening for EntryChange + // and FeatureChange events. + + getEntryGroup ().addEntryGroupChangeListener (this); + getEntryGroup ().addEntryChangeListener (this); + getEntryGroup ().addFeatureChangeListener (this); + + + // find the maximum posible width for the high and low positions + final int sequence_length = getEntryGroup ().getSequenceLength (); + max_base_pos_width = (int)(Math.log (sequence_length)/Math.log (10)) + 1; + + if (max_base_pos_width < 4) { + max_base_pos_width = 4; + } + + repaintCanvas (); + } + + /** + * Remove this component from all the listener lists it is on. + **/ + void stopListening () { + getSelection ().removeSelectionChangeListener (this); + + getEntryGroup ().removeEntryGroupChangeListener (this); + getEntryGroup ().removeEntryChangeListener (this); + getEntryGroup ().removeFeatureChangeListener (this); + } + + /** + * Returns the value of a flag that indicates whether this component can be + * traversed using Tab or Shift-Tab keyboard focus traversal - returns true + * for FeatureDisplay components + **/ +// tjc - deprecated replaced by isFocusable() +//public boolean isFocusTraversable () +//{ +// return true; +//} + + /** + * Set value of the show correlation scores flag. + * @param show_correlation_scores Show correlation scores in the list if + * and only if this argument is true. + **/ + public void setCorrelationScores (final boolean show_correlation_scores) { + if (this.show_correlation_scores != show_correlation_scores) { + this.show_correlation_scores = show_correlation_scores; + repaintCanvas (); + } else { + // do nothing + } + } + + /** + * Get the value of the "show correlation scores" flag. + **/ + public boolean getCorrelationScores () { + return show_correlation_scores; + } + + /** + * Set value of the show /gene flag. + * @param show_gene_names If true this component will show the /gene (really + * Feature.getIDString ()) instead of the key. + **/ + public void setShowGenes (final boolean show_gene_names) { + if (this.show_gene_names != show_gene_names) { + this.show_gene_names = show_gene_names; + repaintCanvas (); + } else { + // do nothing + } + } + + /** + * Get the value of the "show genes" flag. + **/ + public boolean getShowGenes () { + return show_gene_names; + } + + /** + * Set value of the show qualifiers flag. + * @param show_quailfiers If true this component will show all the + * qualifiers after the note. + **/ + public void setShowQualifiers (final boolean show_qualifiers) { + if (this.show_qualifiers != show_qualifiers) { + this.show_qualifiers = show_qualifiers; + repaintCanvas (); + } else { + // do nothing + } + } + + /** + * Get the value of the "show qualifiers" flag. + **/ + public boolean getShowQualifiers () { + return show_qualifiers; + } + + /** + * Set value of the show /product flag. + * @param show_products If true this component will show the /product + * qualifier instead of the /note. + **/ + public void setShowProducts (final boolean show_products) { + if (this.show_products != show_products) { + this.show_products = show_products; + repaintCanvas (); + } else { + // do nothing + } + } + + /** + * Get the value of the "show products" flag. + **/ + public boolean getShowProducts () { + return show_products; + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can update the display if entries + * are added or deleted. + **/ + public void entryGroupChanged (EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.ENTRY_ADDED: + case EntryGroupChangeEvent.ENTRY_ACTIVE: + case EntryGroupChangeEvent.ENTRY_DELETED: + case EntryGroupChangeEvent.ENTRY_INACTIVE: + repaintCanvas (); + break; + } + } + + /** + * Implementation of the FeatureChangeListener interface. + **/ + public void featureChanged (FeatureChangeEvent event) { + if (!isVisible ()) { + return; + } + + repaintCanvas (); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so that we can update the list if features are added + * or deleted. + **/ + public void entryChanged (EntryChangeEvent event) { + if (!isVisible ()) { + return; + } + + repaintCanvas (); + } + + /** + * Implementation of the SelectionChangeListener interface. We listen to + * SelectionChange events so that we can update the list to reflect the + * current selection. + **/ + public void selectionChanged (SelectionChangeEvent event) { + if (!isVisible ()) { + return; + } + + if (event.getSource () == this) { + // don't bother with events we sent ourself + return; + } + + if (getSelection ().getMarkerRange () != null && + event.getType () == SelectionChangeEvent.OBJECT_CHANGED) { + // if the selected range changes we don't care + return; + } + + selection_changed_flag = true; + + repaintCanvas (); + } + + /** + * Return a vector containing the text that is shown in the list - one + * String per line. + **/ + public StringVector getListStrings () { + final StringVector return_vector = new StringVector (); + + final FeatureEnumeration test_enumerator = getEntryGroup ().features (); + + while (test_enumerator.hasMoreFeatures ()) { + final Feature this_feature = test_enumerator.nextFeature (); + + return_vector.add (makeFeatureString (this_feature, true)); + } + + return return_vector; + } + + /** + * Create the scroll bar. + **/ + private void createScrollbars () { + scrollbar = new JScrollBar (Scrollbar.VERTICAL); + scrollbar.setValues (0, 1, 0, + getEntryGroup ().getAllFeaturesCount ()); + scrollbar.setUnitIncrement (1); + scrollbar.setBlockIncrement (1); + scrollbar.addAdjustmentListener (new AdjustmentListener () { + public void adjustmentValueChanged(AdjustmentEvent e) { + setFirstIndex (e.getValue ()); + } + }); + + horiz_scrollbar = new JScrollBar (Scrollbar.HORIZONTAL); + horiz_scrollbar.setValues (0, 1, 0, + getEntryGroup ().getAllFeaturesCount () - 1); + horiz_scrollbar.setUnitIncrement (getFontWidth ()); + horiz_scrollbar.setBlockIncrement (100); + horiz_scrollbar.addAdjustmentListener (new AdjustmentListener () { + public void adjustmentValueChanged (AdjustmentEvent e) { + repaintCanvas (); + } + }); + + getMidPanel ().add (horiz_scrollbar, "South"); + + add (scrollbar, "East"); + } + + /** + * Set the extent, max and value of the horizontal scrollbar + **/ + private void fixHorizScrollBar (final int max_width) { + int old_value = horiz_scrollbar.getValue (); + + horiz_scrollbar.setValues (horiz_scrollbar.getValue (), + getCanvas ().getSize ().width, + 0, max_width * getFontWidth ()); + horiz_scrollbar.setBlockIncrement (getCanvas ().getSize ().width); + } + + /** + * Set the first visible index. + **/ + public void setFirstIndex (final int first_index) { + this.first_index = first_index; + need_to_fix_horiz_scrollbar = true; + repaintCanvas (); + } + + /** + * 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) { + return; + } + + getCanvas ().requestFocus (); + + if (!event.isShiftDown ()) { + getSelection ().clear (); + } + + final int clicked_feature_index = + scrollbar.getValue () + event.getY () / getLineHeight (); + + if (clicked_feature_index < getEntryGroup ().getAllFeaturesCount ()) { + final FeatureVector selected_features = + getSelection ().getAllFeatures (); + + final Feature clicked_feature = + getEntryGroup ().featureAt (clicked_feature_index); + + if (selected_features.contains (clicked_feature)) { + getSelection ().remove (clicked_feature); + getSelection ().removeSegmentsOf (clicked_feature); + } else { + getSelection ().add (clicked_feature); + } + + if (event.getClickCount () == 2) { + makeSelectionVisible (); + + if ((event.getModifiers () & InputEvent.BUTTON2_MASK) != 0 || + event.isAltDown ()) { + if (Options.readWritePossible ()) { + new FeatureEdit (clicked_feature, getEntryGroup (), + getSelection (), + getGotoEventSource ()).show (); + } + } + } + } + } + + /** + * The main paint function for the canvas. An off screen image used for + * double buffering when drawing the canvas. + * @param g The Graphics object of the canvas. + **/ + protected void paintCanvas (Graphics g) { + refresh (); + + if (!isVisible ()) { + return; + } + + if (selection_changed_flag) { + selection_changed_flag = false; + + final FeatureVector selected_features = + getSelection ().getAllFeatures (); + + if (selected_features.size () > 0) { + // set to true if any of the selected features is visible + boolean a_selected_feature_is_visible = false; + + int first_line_in_view = scrollbar.getValue (); + + if (first_line_in_view == -1) { + first_line_in_view = 0; + } + + final int feature_count = getEntryGroup ().getAllFeaturesCount (); + + for (int i = first_line_in_view ; + i < feature_count && i < first_line_in_view + linesInView () ; + ++i) { + final Feature this_feature = getEntryGroup ().featureAt (i); + if (selected_features.contains (this_feature)) { + a_selected_feature_is_visible = true; + break; + } + } + + if (!a_selected_feature_is_visible) { + // make the first selected feature visible + final Feature first_selected_feature = + selected_features.elementAt (0); + + final int index_of_first_selected_feature = + getEntryGroup ().indexOf (first_selected_feature); + + if (index_of_first_selected_feature < scrollbar.getValue () || + index_of_first_selected_feature >= + scrollbar.getValue () + linesInView ()) { + + scrollbar.setValue (index_of_first_selected_feature); + } + } + } + } + + g.setColor (background_colour); + + g.fillRect (0, 0, getCanvasWidth (), getCanvasHeight ()); + + g.setColor (Color.black); + + final int all_feature_count = getEntryGroup ().getAllFeaturesCount (); + + if (all_feature_count == 0) { + fixHorizScrollBar (0); + } else { + final int lines_in_view = linesInView (); + int first_index_in_view = scrollbar.getValue (); + + if (first_index_in_view == -1) { + first_index_in_view = 0; + } + + final int feature_count = getEntryGroup ().getAllFeaturesCount (); + + /* jikes 1.15 bug final */ int last_index_in_view; + + if (lines_in_view < feature_count - first_index_in_view) { + last_index_in_view = first_index_in_view + lines_in_view; + } else { + last_index_in_view = feature_count - 1; + } + + final FeatureVector features_in_view = + getEntryGroup ().getFeaturesInIndexRange (first_index_in_view, + last_index_in_view); + + /** + * The maximum width of the strings we have seen - used to set the + * horiz_scrollbar maximum. + **/ + int max_width = -1; + + for (int i = 0 ; i <= last_index_in_view - first_index_in_view ; ++i) { + final Feature this_feature = features_in_view.elementAt (i); + final String feature_string = makeFeatureString (this_feature, false); + drawFeatureLine (g, this_feature, feature_string,i); + + if (feature_string.length () > max_width) { + max_width = feature_string.length (); + } + } + + if (need_to_fix_horiz_scrollbar) { + fixHorizScrollBar (max_width); + } + } + } + + /** + * Return the number of visible text lines on canvas. + **/ + private int linesInView () { + return getCanvas ().getSize ().height / getLineHeight (); + } + + /** + * Update the scrollbar. + **/ + private void refresh () { + scrollbar.setMaximum (getEntryGroup ().getAllFeaturesCount ()); + final int lines_in_view = linesInView (); + scrollbar.setBlockIncrement (lines_in_view > 0 ? lines_in_view : 1); + scrollbar.setUnitIncrement (1); + scrollbar.setVisibleAmount (linesInView ()); + } + + /** + * Draw the given Feature at the given line of the list, taking the + * selection into account. + **/ + private void drawFeatureLine (final Graphics g, + final Feature feature, + final String feature_string, + final int line) { + final int y_pos = line * getLineHeight (); + + // the width of the coloured blob at the left of the text + final int BOX_WIDTH = getLineHeight (); + + final Color feature_colour = feature.getColour (); + + if (feature_colour == null) { + // default colour is white + g.setColor (Color.white); + } else { + g.setColor (feature_colour); + } + g.fillRect (1 - horiz_scrollbar.getValue (), y_pos + 1, + BOX_WIDTH, getLineHeight () - 1); + + if (getSelection ().contains (feature)) { + // draw in reverse + g.setColor (Color.black); + g.fillRect (BOX_WIDTH + 4 - horiz_scrollbar.getValue (), y_pos, + getCanvas ().getSize ().width + horiz_scrollbar.getValue (), + getLineHeight ()); + g.setColor (background_colour); + } else { + g.setColor (Color.black); + } + + g.setFont (getFont ()); + + g.drawString (feature_string, + BOX_WIDTH + 5 - + horiz_scrollbar.getValue (), + y_pos + getFontAscent ()); + + g.setPaintMode (); + } + + /** + * Return the list index of a feature. + **/ + private int indexOf (Feature feature) { + return getEntryGroup ().indexOf (feature); + } + + /** + * Return a String object suitable for displaying in the list of features. + * @param dont_truncate if true the gene name / key field won't be + * truncated if it is longer than the field width + **/ + private String makeFeatureString (final Feature feature, + final boolean dont_truncate) { + String key_string; + + final int KEY_FIELD_WIDTH = 15; + + if (show_gene_names) { + key_string = feature.getIDString (); + + if (key_string.length () > KEY_FIELD_WIDTH && !dont_truncate) { + key_string = key_string.substring (0, KEY_FIELD_WIDTH); + } + } else { + key_string = feature.getKey ().toString (); + } + + final Marker low_marker = feature.getFirstBaseMarker (); + final Marker high_marker = feature.getLastBaseMarker (); + + final StringBuffer description_string_buffer = new StringBuffer (); + + if (show_products) { + final String product_string = feature.getProductString (); + + if (product_string == null) { + if (feature.isCDS ()) { + description_string_buffer.append ("[no /product]"); + } else { + // description is blank + } + } else { + description_string_buffer.append (product_string); + } + } else { + final String note = feature.getNote (); + + if (note != null && note.length () != 0) { + final int QUALIFIER_COLUMN = 10; + + final String note_string = + padRightWithSpaces (feature.getNote (), QUALIFIER_COLUMN); + + description_string_buffer.append (note_string); + description_string_buffer.append (" "); + } + + if (show_qualifiers) { + description_string_buffer.append (getQualifierString (feature)); + } + } + + final String low_pos; + final String high_pos; + + if (low_marker == null || high_marker == null) { + low_pos = "unknown"; + high_pos = "unknown"; + } else { + if (low_marker.getRawPosition () < high_marker.getRawPosition ()) { + low_pos = String.valueOf (low_marker.getRawPosition ()); + high_pos = String.valueOf (high_marker.getRawPosition ()); + } else { + low_pos = String.valueOf (high_marker.getRawPosition ()); + high_pos = String.valueOf (low_marker.getRawPosition ()); + } + } + + StringBuffer new_list_line = new StringBuffer (); + + new_list_line.append (padRightWithSpaces (key_string, KEY_FIELD_WIDTH)); + new_list_line.append (" "); + + new_list_line.append (padLeftWithSpaces (low_pos, max_base_pos_width)); + new_list_line.append (" "); + new_list_line.append (padLeftWithSpaces (high_pos, max_base_pos_width)); + new_list_line.append (" "); + + if (feature.isForwardFeature ()) { + new_list_line.append (" "); + } else { + new_list_line.append ("c "); + } + + if (show_correlation_scores) { + if (feature.isCDS ()) { + new_list_line.append (getScoresString (feature)); + new_list_line.append (" "); + } else { + new_list_line.append (" "); + if (getBasePlotGroup ().getCodonUsageAlgorithm () != null) { + new_list_line.append (" "); + } + } + } + + new_list_line.append (description_string_buffer); + + return new_list_line.toString (); + } + + /** + * Return the characters width of the Canvas. + **/ + private int getWidthInChars () { + return getCanvas ().getSize ().width / getFontWidth (); + } + + /** + * Return a String containing the given Qualifier and it's values (in EMBL + * format). + * @param start_index ignore the values before this index + **/ + private String formatQualifier (final String qualifier_name, + final Feature feature, + final int start_index) { + final StringBuffer buffer = new StringBuffer (); + + try { + final Qualifier qualifier = feature.getQualifierByName (qualifier_name); + + if (qualifier != null) { + final EntryInformation entry_information = + feature.getEntry ().getEntryInformation (); + + final QualifierInfo qualifier_info = + entry_information.getQualifierInfo (qualifier_name); + + final StringVector qualifier_strings = + StreamQualifier.toStringVector (qualifier_info, + qualifier); + + for (int i = start_index ; i < qualifier_strings.size () ; ++i) { + + final String qualifier_string = qualifier_strings.elementAt (i); + + buffer.append (qualifier_string + " "); + } + } + } catch (InvalidRelationException e) { + // ignore + } + + return buffer.toString (); + } + + /** + * Return a String containing all the qualifiers of the given Feature + * (except /note) in EMBL format. Any /similarity qualifier will come + * first. + **/ + private String getQualifierString (final Feature feature) { + final StringBuffer buffer = new StringBuffer (); + + final QualifierVector qualifiers = feature.getQualifiers (); + + // if there is a /note and it has more than one value put it next (without + // the first value) + final Qualifier note_qualifier = + qualifiers.getQualifierByName ("note"); + + if (note_qualifier != null && note_qualifier.getValues ().size () > 1) { + buffer.append (formatQualifier ("note", feature, 1)); + buffer.append (" "); + } + + // put /similarity before all but the /note qualifier + final Qualifier similarity_qualifier = + qualifiers.getQualifierByName ("similarity"); + + if (similarity_qualifier != null) { + buffer.append (formatQualifier ("similarity", feature, 0)); + buffer.append (" "); + } + + for (int i = 0 ; i < qualifiers.size () ; ++i) { + final Qualifier this_qualifier = qualifiers.elementAt (i); + + final String this_qualifier_name = this_qualifier.getName (); + + if (!this_qualifier_name.equals ("note") && + !this_qualifier_name.equals ("similarity")) { + buffer.append (formatQualifier (this_qualifier_name, feature, 0)); + buffer.append (" "); + } + } + + return buffer.toString (); + } + + /** + * Return a String containing the correlation scores. + **/ + public String getScoresString (final Feature feature) { + final int base_total = feature.getTranslationBases ().length (); + + final int c_total = feature.getBaseCount (Bases.getIndexOfBase ('c')); + final int g_total = feature.getBaseCount (Bases.getIndexOfBase ('g')); + + final int g1_count = + feature.getPositionalBaseCount (0, Bases.getIndexOfBase ('g')); + + final int c3_count = + feature.getPositionalBaseCount (2, Bases.getIndexOfBase ('c')); + final int g3_count = + feature.getPositionalBaseCount (2, Bases.getIndexOfBase ('g')); + + final double c3_score = 100.0 * (3 * c3_count - c_total) / c_total; + final double g1_score = 100.0 * (3 * g1_count - g_total) / g_total; + final double g3_score = 100.0 * (3 * g3_count - g_total) / g_total; + + final double cor1_2_score = feature.get12CorrelationScore (); + + final NumberFormat number_format = NumberFormat.getNumberInstance (); + + number_format.setMaximumFractionDigits (1); + number_format.setMinimumFractionDigits (1); + + final String cor1_2_score_string = number_format.format (cor1_2_score); + final String c3_score_string; + final String g1_score_string; + final String g3_score_string; + + + if (c_total == 0) { + c3_score_string = "ALL"; + } else { + c3_score_string = number_format.format (c3_score); + } + + if (g_total == 0) { + g1_score_string = "ALL"; + } else { + g1_score_string = number_format.format (g1_score); + } + + if (g_total == 0) { + g3_score_string = "ALL"; + } else { + g3_score_string = number_format.format (g3_score); + } + + String codon_usage_score_string = ""; + + final CodonUsageAlgorithm codon_usage_alg = + getBasePlotGroup ().getCodonUsageAlgorithm (); + + if (codon_usage_alg != null) { + number_format.setMaximumFractionDigits (3); + number_format.setMinimumFractionDigits (3); + + codon_usage_score_string = + number_format.format (codon_usage_alg.getFeatureScore (feature)) + " "; + } + + return + codon_usage_score_string + + padRightWithSpaces (cor1_2_score_string, 5) + " " + + padRightWithSpaces (c3_score_string, 5) + " " + + padRightWithSpaces (g1_score_string, 5) + " " + + padRightWithSpaces (g3_score_string, 5); + } + + /** + * Return the given string padded with spaces to the given width. The + * spaces are added on the right of the string. + **/ + private String padRightWithSpaces (final String string, final int width) { + if (string.length () == width) { + return string; + } + + final StringBuffer buffer = new StringBuffer (string); + + for (int i = 0 ; i < width - string.length () ; ++i) { + buffer.append (' '); + } + + return buffer.toString (); + } + + /** + * Return the given string padded with spaces to the given width. The + * spaces are added on the left of the string. + **/ + private String padLeftWithSpaces (final String string, final int width) { + if (string.length () == width) { + return string; + } + + final StringBuffer buffer = new StringBuffer (); + + for (int i = 0 ; i < width - string.length () ; ++i) { + buffer.append (' '); + } + + buffer.append (string); + + return buffer.toString (); + } + + /** + * Return the height each line of the display should be. Each feature will + * be drawn into one line. + **/ + private int getLineHeight () { + return getFontAscent () + 2; + } + + /** + * This variable is true if correlation scores should be shown in the list. + **/ + private boolean show_correlation_scores = false; + + /** + * The JScrollBar for this FeatureList object. + **/ + private JScrollBar scrollbar = null; + + /** + * The JScrollBar for horizontal scrolling in this FeatureList object. + **/ + private JScrollBar horiz_scrollbar = null; + + /** + * The index of the first visible feature in the list. + **/ + private int first_index; + + /** + * This is set to true by selectionChanged () and used by paintCanvas (). + **/ + private boolean selection_changed_flag = false; + + /** + * The colour used to draw the background. + **/ + private Color background_colour = Color.white; + + /** + * If true this component will show Feature.getIDString () (ie /gene or + * /label) instead of the key. + **/ + private boolean show_gene_names = false; + + /** + * If true this component will show the /product qualifier instead of the + * /note field. + **/ + private boolean show_products = false; + + /** + * If true this component will show all the qualifiers after the note. + **/ + private boolean show_qualifiers = false; + + /** + * The is the maximum width of the strings containing the feature start and + * stop positions. Set in the constructor. + **/ + private int max_base_pos_width; + + /** + * Set to true when paintCanvas() needs to call fixHorizScrollBar(). + **/ + private boolean need_to_fix_horiz_scrollbar = true; +} diff --git a/uk/ac/sanger/artemis/components/FeatureListFrame.java b/uk/ac/sanger/artemis/components/FeatureListFrame.java new file mode 100644 index 000000000..ee31a12b6 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureListFrame.java @@ -0,0 +1,195 @@ +/* FeatureListFrame.java + * + * created: Fri Sep 3 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/FeatureListFrame.java,v 1.1 2004-06-09 09:46:41 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A JFrame that contains a FeatureList component and an Close button. + * + * @author Kim Rutherford + * @version $Id: FeatureListFrame.java,v 1.1 2004-06-09 09:46:41 tjc Exp $ + **/ + +public class FeatureListFrame extends JFrame + implements EntryGroupChangeListener { + /** + * Create a new FeatureListFrame component. The constructor does not call + * setVisible (true). + * @param title The title to use for the new JFrame. + * @param feature_list The FeatureList to show. + * @param selection The Selection that the commands in the menus will + * operate on. + * @param entry_group The EntryGroup object where new features/entries will + * be added. + * @param goto_event_source The object that the menu item will call + * makeBaseVisible () on. + **/ + public FeatureListFrame (final String title, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group) { + super (title); + + this.entry_group = entry_group; + + feature_list = new FeatureList (entry_group, selection, goto_event_source, + base_plot_group); + + final Font default_font = Options.getOptions ().getFont (); + + setFont (default_font); + + final JMenuBar menu_bar = new JMenuBar (); + + menu_bar.setFont (default_font); + + setJMenuBar (menu_bar); + + final JMenu file_menu = new JMenu ("File"); + + final JMenuItem close = new JMenuItem ("Close"); + close.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + setVisible (false); + FeatureListFrame.this.dispose (); + feature_list.stopListening (); + } + }); + + file_menu.add (close); + + menu_bar.add (file_menu); + + final JMenu select_menu = + new SelectMenu (this, selection, goto_event_source, entry_group, + base_plot_group); + menu_bar.add (select_menu); + + final JMenu view_menu = + new ViewMenu (this, selection, goto_event_source, entry_group, + base_plot_group); + menu_bar.add (view_menu); + + final JMenu goto_menu = + new GotoMenu (this, selection, goto_event_source, entry_group); + menu_bar.add (goto_menu); + + if (Options.readWritePossible ()) { + final JMenu edit_menu = + new EditMenu (this, selection, goto_event_source, entry_group, + base_plot_group); + menu_bar.add (edit_menu); + + final JMenu write_menu = new WriteMenu (this, selection, entry_group); + menu_bar.add (write_menu); + + final JMenu run_menu = new RunMenu (this, selection); + menu_bar.add (run_menu); + } + + getContentPane ().add (feature_list, "Center"); + + final JPanel panel = new JPanel (); + + final JButton close_button = new JButton ("Close"); + + panel.add (close_button); + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + setVisible (false); + FeatureListFrame.this.dispose (); + feature_list.stopListening (); + } + }); + + getContentPane ().add (panel, "South"); + pack (); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + setVisible (false); + entry_group.removeEntryGroupChangeListener (FeatureListFrame.this); + FeatureListFrame.this.dispose (); + feature_list.stopListening (); + } + }); + + entry_group.addEntryGroupChangeListener (this); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + int screen_height = screen.height; + int screen_width = screen.width; + + if (screen_width <= 700 || screen_height <= 400) { + setSize (screen_width * 9 / 10, screen_height * 9 / 10); + } else { + setSize (700, 400); + } + + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can get rid of the Navigator when the + * EntryGroup is no longer in use (for example when the EntryEdit is + * closed). + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.DONE_GONE: + entry_group.removeEntryGroupChangeListener (this); + dispose (); + break; + } + } + + /** + * Return the FeatureList that this JFrame is displaying. + **/ + public FeatureList getFeatureList () { + return feature_list; + } + + /** + * The FeatureList that this JFrame is displaying. + **/ + private FeatureList feature_list; + + /** + * The EntryGroup that was passed to the constructor. + **/ + private EntryGroup entry_group; +} diff --git a/uk/ac/sanger/artemis/components/FeaturePlot.java b/uk/ac/sanger/artemis/components/FeaturePlot.java new file mode 100644 index 000000000..1707d46d8 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeaturePlot.java @@ -0,0 +1,389 @@ +/* FeaturePlot.java + * + * created: Wed Dec 16 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeaturePlot.java,v 1.1 2004-06-09 09:46:42 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.plot.*; +import java.awt.*; +import java.awt.event.*; + +/** + * The components of this class display a plot of a FeatureAlgorithm for a + * particular feature. + * + * @author Kim Rutherford + * @version $Id: FeaturePlot.java,v 1.1 2004-06-09 09:46:42 tjc Exp $ + **/ + +public class FeaturePlot extends Plot + implements DisplayAdjustmentListener, FeatureChangeListener { + /** + * Create a new FeatureDisplay object. + * @param algorithm The object that will generate the values we plot in + * this component. + **/ + public FeaturePlot (FeatureAlgorithm algorithm) { + super (algorithm, true); // true means draw a scale at the bottom of + // the graph + + getFeature ().addFeatureChangeListener (this); + + recalculate_flag = true; + } + + /** + * Used by getPreferredSize () and getMinimumSize (); + **/ + private final static int HEIGHT; + + static { + final Integer feature_plot_height = + Options.getOptions ().getIntegerProperty ("feature_plot_height"); + + if (feature_plot_height == null) { + HEIGHT = 160; + } else { + HEIGHT = feature_plot_height.intValue (); + } + } + + /** + * Overridden to set the component height to 150. + **/ + public Dimension getPreferredSize() { + return (new Dimension(getSize ().width, HEIGHT)); + } + + /** + * Overridden to set the component height to 150. + **/ + public Dimension getMinimumSize() { + return (new Dimension(getSize ().width, HEIGHT)); + } + + /** + * Remove this object as a feature change listener. (Called by + * FeaturePlotGroup) + **/ + void stopListening () { + getFeature ().removeFeatureChangeListener (this); + } + + /** + * Implementation of the DisplayAdjustmentListener interface. Invoked when + * a component scrolls or changes the scale. + **/ + public void displayAdjustmentValueChanged (DisplayAdjustmentEvent event) { + start_base = event.getStart (); + end_base = event.getEnd (); + width_in_bases = event.getWidthInBases (); + + recalculate_flag = true; + + repaintCanvas (); + } + + /** + * Implementation of the FeatureChangeListener interface. + * @param event The change event. + **/ + public void featureChanged (FeatureChangeEvent event) { + recalculate_flag = true; + } + + /** + * Return the algorithm that was passed to the constructor. + **/ + public FeatureAlgorithm getFeatureAlgorithm () { + return (FeatureAlgorithm) super.getAlgorithm (); + } + + /** + * Return the new start base to display, from the last event. + **/ + private int getStart () { + return start_base; + } + + /** + * Return the new end base to display, from the last event. + **/ + private int getEnd () { + return end_base; + } + + /** + * Return the width in bases of the display, from the last event. + **/ + private int getWidthInBases () { + return width_in_bases; + } + + /** + * This array is used by drawGraph (). It is reallocated when the scale + * changes. + **/ + private float [][] value_array_array = null; + + /** + * The number of bases to step before each evaluation of the algorithm. + * (Set by recalculateValues ()). + **/ + private int step_size = 0; + + /** + * The maximum of the values in value_array_array. + **/ + private float min_value = Float.MAX_VALUE; + + /** + * The minimum of the values in value_array_array. + **/ + private float max_value = Float.MIN_VALUE; + + /** + * Recalculate the values in value_array_array, step_size, min_value and + * max_value. + **/ + protected void recalculateValues () { + final Float algorithm_minimum = getAlgorithm ().getMinimum (); + final Float algorithm_maximum = getAlgorithm ().getMaximum (); + + // use the Algorithm specified maximum if there is one - otherwise + // calculate it + if (algorithm_maximum == null) { + max_value = Float.MIN_VALUE; + } else { + max_value = algorithm_maximum.floatValue (); + } + + // use the Algorithm specified minimum if there is one - otherwise + // calculate it + if (algorithm_minimum == null) { + min_value = Float.MAX_VALUE; + } else { + min_value = algorithm_minimum.floatValue (); + } + + final int window_size = getWindowSize (); + + final Integer default_step_size = + getAlgorithm ().getDefaultStepSize (window_size); + + if (default_step_size == null) { + step_size = window_size; + } else { + if (default_step_size.intValue () < window_size) { + step_size = default_step_size.intValue (); + } else { + step_size = window_size; + } + } + + final int unit_count = getEnd () - getStart (); + + // the number of plot points in the graph + final int number_of_values = + (unit_count - (window_size - step_size)) / step_size; + + if (number_of_values < 2) { + // there is nothing to plot + value_array_array = null; + return; + } + + // the number of values that getValues () will return + final int get_values_return_count = + getFeatureAlgorithm ().getValueCount (); + + if (value_array_array == null) { + value_array_array = new float [get_values_return_count][]; + } + + if (value_array_array[0] == null || + value_array_array[0].length != number_of_values) { + for (int i = 0 ; i < value_array_array.length ; ++i) { + value_array_array[i] = new float [number_of_values]; + } + } else { + // reuse the previous arrays + } + + if (!isVisible ()) { + return; + } + + float [] temp_values = new float [get_values_return_count]; + + for (int i = 0 ; i < number_of_values ; ++i) { + getFeatureAlgorithm ().getValues (getStart () + i * step_size, + getStart () + i * step_size + + window_size - 1, + temp_values); + + for (int value_index = 0 ; + value_index < get_values_return_count ; + ++value_index) { + final float current_value = temp_values[value_index]; + + value_array_array[value_index][i] = current_value; + + // use the Algorithm specified maximum if there is one - otherwise + // calculate it + if (algorithm_maximum == null) { + if (current_value > max_value) { + max_value = current_value; + } + } + + // use the Algorithm specified minimum if there is one - otherwise + // calculate it + if (algorithm_minimum == null) { + if (current_value < min_value) { + min_value = current_value; + } + } + } + } + + recalculate_flag = false; + } + + /** + * Redraw the graph on the canvas using the algorithm, start_base and + * end_base. This method plots BaseWindowAlgorithm objects only. + * @param g The object to draw into. + **/ + protected void drawMultiValueGraph (Graphics g) { + if (recalculate_flag) { + recalculateValues (); + } + + if (value_array_array == null) { + // there is nothing to draw - probably because the sequence is too short + drawMinMax (g, 0, 1); + + return; + } + + final int window_size = getWindowSize (); + + // the number of values to plot at each x position + final int get_values_return_count = + getFeatureAlgorithm ().getValueCount (); + + // the number of x positions to plot points at + final int number_of_values = value_array_array[0].length; + + if (number_of_values > 1) { + drawGlobalAverage (g, min_value, max_value); + } + + for (int value_index = 0 ; + value_index < get_values_return_count ; + ++value_index) { + if (get_values_return_count == 1) { + g.setColor (Color.black); + } else { + switch (value_index) { + case 0: + g.setColor (new Color (255, 0, 0)); + break; + case 1: + g.setColor (new Color (100, 255, 100)); + break; + case 2: + g.setColor (new Color (0, 0, 255)); + break; + default: + g.setColor (Color.black); + } + } + + drawPoints (g, min_value, max_value, step_size, window_size, + getCanvas ().getSize ().width, + 0, // no offset. + value_array_array[value_index]); + } + + drawMinMax (g, min_value, max_value); + + drawScaleLine (g, getStart (), getEnd ()); + + final int cross_hair_position = getCrossHairPosition (); + + if (cross_hair_position >= 0) { + if (cross_hair_position >= getTranslation ().length ()) { + cancelCrossHairs (); + } else { + drawCrossHair (g, cross_hair_position, + String.valueOf (getPointPosition (cross_hair_position + + getStart () - 1)), + 0); + } + } + } + + /** + * Get the position in the Feature of the given canvas x position. This + * amino acid position is the label used when the user clicks the mouse in + * on the canvas (see drawCrossHair ()). + **/ + protected int getPointPosition (final int canvas_x_position) { + return canvas_x_position + 1; + } + + /** + * Return the feature that this component is plotting. + **/ + private Feature getFeature () { + return getFeatureAlgorithm ().getFeature (); + } + + /** + * Return the translation of the bases of the feature we are plotting. + **/ + private AminoAcidSequence getTranslation () { + return getFeature ().getTranslation (); + } + + /** + * The start base to plot, as obtained from the DisplayAdjustmentEvent. + **/ + private int start_base; + + /** + * The end base to plot, as obtained from the DisplayAdjustmentEvent. + **/ + private int end_base; + + /** + * The width in bases of the display, as obtained from the + * DisplayAdjustmentEvent. + **/ + private int width_in_bases; +} diff --git a/uk/ac/sanger/artemis/components/FeaturePlotGroup.java b/uk/ac/sanger/artemis/components/FeaturePlotGroup.java new file mode 100644 index 000000000..ca1244457 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeaturePlotGroup.java @@ -0,0 +1,337 @@ +/* FeaturePlotGroup.java + * + * created: Wed Dec 16 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeaturePlotGroup.java,v 1.1 2004-06-09 09:46:43 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.plot.*; + +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * This is a super-component containing several FeaturePlot components, each + * of which can toggled off and on. The component contains a row of toggle + * buttons and then the FeaturePlot components below. + * + * @author Kim Rutherford + * @version $Id: FeaturePlotGroup.java,v 1.1 2004-06-09 09:46:43 tjc Exp $ + **/ + +public class FeaturePlotGroup extends JFrame + implements EntryChangeListener, FeatureChangeListener { + /** + * Create a new FeaturePlotGroup component for the given feature. + **/ + public FeaturePlotGroup (Feature feature) { + super ("Graphs for: " + feature.getIDString ()); + this.feature = feature; + this.entry = feature.getEntry (); + + // don't repeat an algorithm in this array. + final FeatureAlgorithm [] plot_value_producers = { + new HydrophobicityAlgorithm (getFeature ()), + new HydrophilicityAlgorithm (getFeature ()), + new CoilFeatureAlgorithm (getFeature ()) + }; + + final Font font = Options.getOptions ().getFont (); + + setFont (font); + + GridBagLayout gridbag = new GridBagLayout(); + getContentPane ().setLayout (gridbag); + + GridBagConstraints c = new GridBagConstraints(); + + c.anchor = GridBagConstraints.NORTH; + c.gridwidth = GridBagConstraints.REMAINDER; + c.gridheight = 1; + c.weightx = 1; + c.fill = GridBagConstraints.BOTH; + c.weighty = 1; +// c.insets = new Insets (0,0,5,0); + + for (int i = 0 ; i < plot_value_producers.length ; ++i) { + final FeatureAlgorithm this_algorithm = plot_value_producers[i]; + + final FeaturePlot new_feature_plot = new FeaturePlot (this_algorithm); + + gridbag.setConstraints (new_feature_plot, c); + getContentPane ().add (new_feature_plot); + new_feature_plot.setVisible (true); + } + + getFeature ().getEntry ().addEntryChangeListener (this); + getFeature ().addFeatureChangeListener (this); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + stopListening (); + FeaturePlotGroup.this.dispose (); + } + }); + + addComponentListener (new ComponentAdapter () { + public void componentResized (ComponentEvent event) { + fixScrollbar (); + fireAdjustmentEvent (scrollbar.getValue ()); + } + }); + + int new_x_size = feature.getTranslation ().length () + SCROLL_BAR_SIZE; + + if (new_x_size > 1000) { + new_x_size = 1000; + } + + if (new_x_size < 300) { + new_x_size = 300; + } + + scrollbar = new JScrollBar (Scrollbar.HORIZONTAL); + c.fill = GridBagConstraints.HORIZONTAL; + c.weighty = 0; + gridbag.setConstraints (scrollbar, c); + scrollbar.addAdjustmentListener (new AdjustmentListener () { + public void adjustmentValueChanged(AdjustmentEvent e) { + fireAdjustmentEvent (e.getValue ()); + } + }); + getContentPane ().add (scrollbar); + + final Component [] children = getContentPane ().getComponents (); + + for (int i = 0 ; i < children.length ; ++i) { + if (children[i] instanceof FeaturePlot) { + addDisplayAdjustmentListener ((FeaturePlot)children[i]); + } + } + + + bottom_button_panel = new JPanel (); + c.fill = GridBagConstraints.HORIZONTAL; + c.weighty = 0; + gridbag.setConstraints (bottom_button_panel, c); + getContentPane ().add (bottom_button_panel); + + close_button = new JButton ("Close"); + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + stopListening (); + FeaturePlotGroup.this.dispose (); + } + }); + + bottom_button_panel.add (close_button); + + pack (); + + // give each FeaturePlot component a height of 200 + setSize (new_x_size, getSize ().height); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + show (); + + fixScrollbar (); + + fireAdjustmentEvent (1); + } + + /** + * Fudge factor to take the scroll bar width into account when creating + * windows and scrollbars. + **/ + private final int SCROLL_BAR_SIZE = 100; + + /** + * Remove this object as a entry change listener and then call + * stopListening () on each FeaturePlot component. + **/ + private void stopListening () { + getEntry ().removeEntryChangeListener (this); + + final Component [] children = getComponents (); + + for (int i = 0 ; i < children.length ; ++i) { + if (children[i] instanceof FeaturePlot) { + ((FeaturePlot)children[i]).stopListening (); + } + } + + getFeature ().getEntry ().removeEntryChangeListener (this); + getFeature ().removeFeatureChangeListener (this); + } + + /** + * Set the scrollbar maximum, minimum and value. + **/ + private void fixScrollbar () { + final int feature_length = getFeature ().getTranslation ().length (); + + int scroll_max = feature_length; + + if (scroll_max < 1) { + scroll_max = 1; + } + + scrollbar.setValues (1, getSize ().width, + 1, scroll_max + SCROLL_BAR_SIZE); + + scrollbar.setBlockIncrement (getSize ().width); + } + + /** + * Adds the specified event adjustemnt listener to receive adjustment + * change events from this object. + * @param l the event change listener. + **/ + private void addDisplayAdjustmentListener (DisplayAdjustmentListener l) { + adjustment_listener_list.addElement (l); + } + + /** + * Removes the specified event listener so that it no longer receives + * adjustment change events from this object. + * @param l the event change listener. + **/ + private void removeDisplayAdjustmentListener (DisplayAdjustmentListener l) { + adjustment_listener_list.removeElement (l); + } + + /** + * Implementation of the FeatureChangeListener interface. + * @param event The change event. + **/ + public void featureChanged (FeatureChangeEvent event) { + fixScrollbar (); + fireAdjustmentEvent (scrollbar.getValue ()); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so we can delete this component if the feature gets + * deleted. + **/ + public void entryChanged (EntryChangeEvent event) { + switch (event.getType ()) { + case EntryChangeEvent.FEATURE_DELETED: + if (event.getFeature () == getFeature ()) { + stopListening (); + dispose (); + } + break; + default: + // do nothing; + break; + } + } + + /** + * Send a DisplayAdjustmentEvent to the objects that are listening for it. + **/ + private void fireAdjustmentEvent (final int scroll_value) { + final int feature_length = getFeature ().getTranslation ().length (); + + int end_value = scroll_value + getSize ().width; + + if (end_value > feature_length) { + end_value = feature_length; + } + + final DisplayAdjustmentEvent event = + new DisplayAdjustmentEvent (this, + scroll_value, + end_value, + getSize ().width, + 1, + 0,// this arg will be ignored + false, + DisplayAdjustmentEvent.SCALE_ADJUST_EVENT); + + final Vector targets; + + // copied from a book - synchronising the whole method might cause a + // deadlock + synchronized (this) { + targets = (Vector) adjustment_listener_list.clone (); + } + + for ( int i = 0 ; i < targets.size () ; ++i ) { + DisplayAdjustmentListener target = + (DisplayAdjustmentListener) targets.elementAt (i); + + target.displayAdjustmentValueChanged (event); + } + } + + /** + * Return the feature that this component is plotting. + **/ + private Feature getFeature () { + return feature; + } + + /** + * Return the Entry that contains the Feature this object is displaying. + **/ + private Entry getEntry () { + return entry; + } + + /** + * The feature we are plotting. + **/ + private Feature feature; + + /** + * The Entry that contains the Feature this object is displaying. + **/ + private Entry entry; + + /** + * Pressing this button will distroy the JFrame. + **/ + private JButton close_button; + + /** + * A JPanel to hold the buttons. + **/ + private JPanel bottom_button_panel; + + private JScrollBar scrollbar; + + /** + * A vector of those objects listening for adjustment events. + **/ + final private Vector adjustment_listener_list = new Vector (); +} diff --git a/uk/ac/sanger/artemis/components/FeaturePopup.java b/uk/ac/sanger/artemis/components/FeaturePopup.java new file mode 100644 index 000000000..f978e2a63 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeaturePopup.java @@ -0,0 +1,758 @@ +/* FeaturePopup.java + * + * created: Wed Oct 21 1998 + * + * This file is part of Artemis + * + * Copyright(C) 1998,1999,2000,2001,2002 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/FeaturePopup.java,v 1.1 2004-06-09 09:46:45 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.util.StringVector; + +import java.io.*; +import java.awt.MenuItem; +import java.awt.CheckboxMenuItem; +import java.awt.event.*; +import javax.swing.*; + +/** + * FeaturePopup class + * + * @author Kim Rutherford + * @version $Id: FeaturePopup.java,v 1.1 2004-06-09 09:46:45 tjc Exp $ + * + **/ + +public class FeaturePopup extends JPopupMenu +{ + + /** + * The reference of the EntryGroup object that was passed to the + * constructor. + **/ + private EntryGroup entry_group; + + /** + * This is the Selection object that was passed to the constructor. + **/ + final private Selection selection; + + /** + * This is a reference to the GotoEventSource object that was passed to the + * constructor. + **/ + private GotoEventSource goto_event_source; + + /** + * The reference of the object that created this popup. + **/ + private DisplayComponent owner; + + /** + * If the parent component of this popup is a FeatureDisplay then this will + * contain it's reference. + **/ + private FeatureDisplay feature_display = null; + + /** + * If the parent component of this popup is a FeatureList then this will + * contain it's reference. + **/ + private FeatureList feature_list = null; + + /** + * Set by the constructor to be the(possibly) empty vector of selected + * features. + **/ + private FeatureVector selection_features; + + /** + * Set by the constructor to be the(possibly) empty vector of selected + * features. + **/ + private FeatureSegmentVector selection_segments; + + private JCheckBoxMenuItem show_labels_item = null; + private JCheckBoxMenuItem one_line_per_entry_item = null; + private JCheckBoxMenuItem show_forward_frame_lines_item = null; + private JCheckBoxMenuItem show_reverse_frame_lines_item = null; + private JCheckBoxMenuItem show_start_codons_item = null; + private JCheckBoxMenuItem show_stop_codons_item = null; + private JCheckBoxMenuItem show_feature_arrows_item = null; + private JCheckBoxMenuItem show_feature_borders_item = null; + private JCheckBoxMenuItem frame_features_item = null; + private JCheckBoxMenuItem source_features_item = null; + private JCheckBoxMenuItem rev_comp_display_item = null; + private JCheckBoxMenuItem base_colours_item = null; + private JCheckBoxMenuItem correlation_scores_item = null; + private JCheckBoxMenuItem show_genes_item = null; + private JCheckBoxMenuItem show_products_item = null; + private JCheckBoxMenuItem show_qualifiers_item = null; + private JMenuItem entry_group_menu_item = null; + private JMenuItem select_menu_item = null; + private JMenuItem add_menu_item = null; + private JMenuItem view_menu_item = null; + private JMenuItem edit_menu_item = null; + private JMenuItem goto_menu_item = null; + private JMenuItem write_menu_item = null; + private JMenuItem run_menu_item = null; + private JMenuItem broadcast_item = null; + private JMenuItem raise_feature_item = null; + private JMenuItem lower_feature_item = null; + private JMenuItem smallest_to_front_item = null; + private JMenuItem zoom_to_selection_item = null; + private JMenuItem score_cutoffs_item = null; + private JMenuItem select_visible_range = null; + private JMenuItem save_feature_list_item = null; + private JMenuItem select_visible_features = null; + + private BasePlotGroup base_plot_group = null; + + /** + * Create a new FeaturePopup object. + * @param owner The component where this popup was popped up from. + * @param selection The selection to use for this popup. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public FeaturePopup(final DisplayComponent owner, + final EntryGroup entry_group, + final Selection selection, + final GotoEventSource goto_event_source, + final BasePlotGroup base_plot_group) + { + super(getMenuName(owner)); + + this.owner = owner; + this.entry_group = entry_group; + this.selection = selection; + this.goto_event_source = goto_event_source; + this.base_plot_group = base_plot_group; + + selection_features = selection.getSelectedFeatures(); + selection_segments = selection.getSelectedSegments(); + + makeSubMenus(); + + addGenericItems(); + + if(owner instanceof FeatureDisplay) + { + feature_display =(FeatureDisplay) owner; + addFeatureDisplayItems(); + } + else + { + // must be a FeatureList + feature_list =(FeatureList) owner; + addFeatureListItems(); + } + + maybeAdd(raise_feature_item); + maybeAdd(lower_feature_item); + maybeAdd(smallest_to_front_item); + maybeAdd(zoom_to_selection_item); + maybeAdd(select_visible_range); + maybeAdd(select_visible_features); + maybeAdd(score_cutoffs_item); + maybeAdd(save_feature_list_item); + addSeparator(); + maybeAdd(entry_group_menu_item); + maybeAdd(select_menu_item); + maybeAdd(goto_menu_item); + maybeAdd(view_menu_item); + maybeAdd(edit_menu_item); + maybeAdd(add_menu_item); + maybeAdd(write_menu_item); + maybeAdd(run_menu_item); + addSeparator(); + maybeAdd(show_labels_item); + maybeAdd(one_line_per_entry_item); + maybeAdd(show_forward_frame_lines_item); + maybeAdd(show_reverse_frame_lines_item); + maybeAdd(show_start_codons_item); + maybeAdd(show_stop_codons_item); + maybeAdd(show_feature_arrows_item); + maybeAdd(show_feature_borders_item); + maybeAdd(frame_features_item); + maybeAdd(source_features_item); + maybeAdd(rev_comp_display_item); + maybeAdd(base_colours_item); + maybeAdd(correlation_scores_item); + maybeAdd(show_genes_item); + maybeAdd(show_qualifiers_item); + maybeAdd(show_products_item); + addSeparator(); + maybeAdd(broadcast_item); + } + + /** + * Rename the name String to use for this JMenu. + **/ + private static String getMenuName(final DisplayComponent owner) + { + if(owner instanceof FeatureDisplay) + return "Feature Viewer JMenu"; + else + return "Feature List JMenu"; + } + + /** + * Add an item only if it isn't null. + **/ + private void maybeAdd(JMenuItem item) + { + if(item != null) + add(item); + } + + /** + * Create those menu items that are relevant to all components. + **/ + private void addGenericItems() + { + if(selection_features.size() > 0 || selection_segments.size() > 0) { + + } + } + + /** + * Create the Edit, Add and View sub menus. + **/ + public void makeSubMenus() + { + final JFrame frame = owner.getParentFrame(); + entry_group_menu_item = new EntryGroupMenu(frame, getEntryGroup()); + + select_menu_item = new SelectMenu(frame, selection, + getGotoEventSource(), + getEntryGroup(), + base_plot_group); + + view_menu_item = new ViewMenu(frame, selection, + getGotoEventSource(), + getEntryGroup(), + base_plot_group); + + goto_menu_item = new GotoMenu(frame, selection, + getGotoEventSource(), + getEntryGroup()); + + if(Options.readWritePossible()) + { + edit_menu_item = new EditMenu(frame, selection, + getGotoEventSource(), + getEntryGroup(), + base_plot_group); + if(entry_group instanceof SimpleEntryGroup) + add_menu_item = new AddMenu(frame, selection, + getEntryGroup(), + getGotoEventSource(), + base_plot_group); + + write_menu_item = new WriteMenu(frame, selection, + getEntryGroup()); + if(Options.isUnixHost()) + run_menu_item = new RunMenu(frame, selection); + } + } + + /** + * Create those menu items that are relevant only to FeatureDisplay objects. + **/ + private void addFeatureDisplayItems() + { + show_start_codons_item = new JCheckBoxMenuItem("Start Codons"); + show_start_codons_item.setState(feature_display.getShowStartCodons()); + show_start_codons_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + feature_display.setShowStartCodons(show_start_codons_item.getState()); + } + }); + + show_stop_codons_item = new JCheckBoxMenuItem("Stop Codons"); + show_stop_codons_item.setState(feature_display.getShowStopCodons()); + show_stop_codons_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + feature_display.setShowStopCodons(show_stop_codons_item.getState()); + } + }); + + show_feature_arrows_item = new JCheckBoxMenuItem("Feature Arrows"); + show_feature_arrows_item.setState(feature_display.getShowFeatureArrows()); + show_feature_arrows_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + feature_display.setShowFeatureArrows(show_feature_arrows_item.getState()); + } + }); + + show_feature_borders_item = new JCheckBoxMenuItem("Feature Borders"); + show_feature_borders_item.setState(feature_display.getShowFeatureBorders()); + show_feature_borders_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + feature_display.setShowFeatureBorders(show_feature_borders_item.getState()); + } + }); + + show_labels_item = new JCheckBoxMenuItem("Feature Labels"); + show_labels_item.setState(feature_display.getShowLabels()); + show_labels_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_display.setShowLabels(show_labels_item.getState()); + } + }); + + one_line_per_entry_item = new JCheckBoxMenuItem("One Line Per Entry"); + one_line_per_entry_item.setState(feature_display.getOneLinePerEntryFlag()); + one_line_per_entry_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + final boolean new_state = one_line_per_entry_item.getState(); + if(new_state && getEntryGroup().size() > 8) + feature_display.setShowLabels(false); + feature_display.setOneLinePerEntry(new_state); + } + }); + + show_forward_frame_lines_item = + new JCheckBoxMenuItem("Forward Frame Lines"); + show_forward_frame_lines_item.setState(feature_display.getShowForwardFrameLines()); + show_forward_frame_lines_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_display.setShowForwardFrameLines(show_forward_frame_lines_item.getState()); + } + }); + + show_reverse_frame_lines_item = + new JCheckBoxMenuItem("Reverse Frame Lines"); + show_reverse_frame_lines_item.setState(feature_display.getShowReverseFrameLines()); + show_reverse_frame_lines_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_display.setShowReverseFrameLines(show_reverse_frame_lines_item.getState()); + } + }); + + frame_features_item = new JCheckBoxMenuItem("All Features On Frame Lines"); + frame_features_item.setState(feature_display.getFrameFeaturesFlag()); + frame_features_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_display.setFrameFeaturesFlag(frame_features_item.getState()); + } + }); + + source_features_item = new JCheckBoxMenuItem("Show Source Features"); + source_features_item.setState(feature_display.getShowSourceFeatures()); + source_features_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_display.setShowSourceFeatures(source_features_item.getState()); + } + }); + + rev_comp_display_item = new JCheckBoxMenuItem("Flip Display"); + + rev_comp_display_item.setState(feature_display.isRevCompDisplay()); + rev_comp_display_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_display.setRevCompDisplay(rev_comp_display_item.getState()); + } + }); + + base_colours_item = new JCheckBoxMenuItem("Colourise Bases"); + base_colours_item.setState(feature_display.getShowBaseColours()); + base_colours_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_display.setShowBaseColours(base_colours_item.getState()); + } + }); + + smallest_to_front_item = + new JMenuItem("Smallest Features In Front"); + smallest_to_front_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + // clear the selection because selected features will always be on + // top - which is not usually what is wanted + selection.clear(); + feature_display.smallestToFront(); + } + }); + + score_cutoffs_item = new JMenuItem("Set Score Cutoffs ..."); + + score_cutoffs_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + final ScoreChangeListener minimum_listener = + new ScoreChangeListener() + { + public void scoreChanged(final ScoreChangeEvent event) + { + feature_display.setMinimumScore(event.getValue()); + } + }; + + final ScoreChangeListener maximum_listener = + new ScoreChangeListener() + { + public void scoreChanged(final ScoreChangeEvent event) + { + feature_display.setMaximumScore(event.getValue()); + } + }; + + final ScoreChanger score_changer = + new ScoreChanger("Score Cutoffs", + minimum_listener, maximum_listener, + 0, 100); + + score_changer.setVisible(true); + } + }); + + if(selection_features.size() > 0 || selection_segments.size() > 0) + { + raise_feature_item = new JMenuItem("Raise Selected Features"); + raise_feature_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + raiseSelection(); + } + }); + + lower_feature_item = new JMenuItem("Lower Selected Features"); + lower_feature_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + lowerSelection(); + } + }); + } + + if(!selection.isEmpty()) + { + zoom_to_selection_item = new JMenuItem("Zoom to Selection"); + zoom_to_selection_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + zoomToSelection((FeatureDisplay) owner); + } + }); + } + + + select_visible_range = + new JMenuItem("Select Visible Range"); + select_visible_range.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + selection.setMarkerRange(feature_display.getVisibleMarkerRange()); + } + }); + + select_visible_features = + new JMenuItem("Select Visible Features"); + select_visible_features.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + selection.set(feature_display.getCurrentVisibleFeatures()); + } + }); + } + + /** + * Create those menu items that are relevant only to FeatureList objects. + **/ + private void addFeatureListItems() + { + if(Options.getOptions().readWritePossible()) + { + save_feature_list_item = new JMenuItem("Save List To File ..."); + save_feature_list_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + saveFeatureList(); + } + }); + } + + correlation_scores_item = new JCheckBoxMenuItem("Show Correlation Scores"); + correlation_scores_item.setState(feature_list.getCorrelationScores()); + correlation_scores_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_list.setCorrelationScores(correlation_scores_item.getState()); + } + }); + + show_genes_item = new JCheckBoxMenuItem("Show Gene Names"); + show_genes_item.setState(feature_list.getShowGenes()); + show_genes_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_list.setShowGenes(show_genes_item.getState()); + } + }); + + show_products_item = new JCheckBoxMenuItem("Show Products"); + show_products_item.setState(feature_list.getShowProducts()); + show_products_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + if(show_products_item.getState()) + feature_list.setShowQualifiers(false); + + feature_list.setShowProducts(show_products_item.getState()); + } + }); + + show_qualifiers_item = new JCheckBoxMenuItem("Show Qualifiers"); + show_qualifiers_item.setState(feature_list.getShowQualifiers()); + show_qualifiers_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + feature_list.setShowQualifiers(show_qualifiers_item.getState()); + if(show_qualifiers_item.getState()) + feature_list.setShowProducts(false); + } + }); + } + + /** + * Save the text of the feature list to a file. + **/ + private void saveFeatureList() + { + final JFrame frame = owner.getParentFrame(); + final StickyFileChooser file_dialog = new StickyFileChooser(); + + file_dialog.setDialogTitle("Choose save file ..."); + file_dialog.setDialogType(JFileChooser.SAVE_DIALOG); + final int status = file_dialog.showOpenDialog(frame); + + if(status != JFileChooser.APPROVE_OPTION || + file_dialog.getSelectedFile() == null) + return; + + final File write_file = + new File(file_dialog.getCurrentDirectory(), + file_dialog.getSelectedFile().getName()); + + if(write_file.exists()) + { + final YesNoDialog yes_no_dialog = + new YesNoDialog(frame, + "this file exists: " + write_file + + " overwrite it?"); + if(yes_no_dialog.getResult()) + { + // yes - continue + } + else + return; + } + + try + { + final PrintWriter writer = + new PrintWriter(new FileWriter(write_file)); + + final StringVector list_strings = feature_list.getListStrings(); + + for(int i = 0 ; i < list_strings.size() ; ++i) + writer.println(list_strings.elementAt(i)); + + writer.close(); + } + catch(IOException e) + { + new MessageDialog(frame, "error while writing: " + e.getMessage()); + } + } + + /** + * Raise the selected features. (FeatureDisplay only.) + **/ + private void raiseSelection() + { + final FeatureVector features_to_raise = selection.getAllFeatures(); + + for(int i = 0 ; i < features_to_raise.size() ; ++i) + { + final Feature selection_feature = features_to_raise.elementAt(i); + feature_display.raiseFeature(selection_feature); + } + } + + /** + * Lower the selected features. (FeatureDisplay only.) + **/ + private void lowerSelection() + { + final FeatureVector features_to_lower = selection.getAllFeatures(); + + for(int i = 0 ; i < features_to_lower.size() ; ++i) + { + final Feature selection_feature = features_to_lower.elementAt(i); + feature_display.lowerFeature(selection_feature); + } + } + + /** + * Zoom the FeatureDisplay to the selection. + **/ + static void zoomToSelection(final FeatureDisplay feature_display) + { + final Selection selection = feature_display.getSelection(); + + if(selection.isEmpty()) + return; + + // why bother in this case? + if(feature_display.getEntryGroup().getSequenceLength() < 1000) + return; + + int first_base; + int last_base; + + final FeatureSegmentVector segments = selection.getSelectedSegments(); + + if(segments.size() == 1) + { + // special case - zoom to the feature instead + first_base = segments.elementAt(0).getFeature().getRawFirstBase(); + last_base = segments.elementAt(0).getFeature().getRawLastBase(); + } + else + { + first_base = selection.getLowestBaseOfSelection().getRawPosition(); + last_base = selection.getHighestBaseOfSelection().getRawPosition(); + } + + if(first_base < 250) + first_base = 250; + else + first_base -= 250; + + last_base += 250; + + feature_display.setFirstAndLastBase(first_base, last_base); + } + + /** + * Return the EntryGroup object that this FeatureDisplay is displaying. + **/ + private EntryGroup getEntryGroup() + { + return entry_group; + } + + /** + * Return an object that implements the GotoEventSource interface and is + * for the sequence that this DisplayComponent is displaying. + **/ + public GotoEventSource getGotoEventSource() + { + return goto_event_source; + } + + /** + * Return a new CheckboxMenuItem unless the VM is 1.2 or worse(ie. 1.3 or + * 1.4) on GNU/Linux, in which case return a new ArtemisCheckboxMenuItem + **/ + private MenuItem makeCheckboxMenuItem(final String item_name) + { + if(Options.getOptions().isBuggyLinuxVM() && + Options.getOptions().getPropertyTruthValue("buggy_linux_vm_fix")) + return new ArtemisCheckboxMenuItem(item_name); + else + return new CheckboxMenuItem(item_name); + } + + /** + * Add a ItemListener to a ArtemisCheckboxMenuItem or a CheckboxMenuItem. + **/ + private void addMenuItemListener(final MenuItem menu_item, + final ItemListener listener) + { + if(menu_item instanceof ArtemisCheckboxMenuItem) + ((ArtemisCheckboxMenuItem)menu_item).addItemListener(listener); + else + ((CheckboxMenuItem)menu_item).addItemListener(listener); + } + + /** + * Set the state of a ArtemisCheckboxMenuItem or a CheckboxMenuItem. + **/ + private void setCheckboxMenuItemState(final MenuItem menu_item, + final boolean state) + { + if(menu_item instanceof ArtemisCheckboxMenuItem) + ((ArtemisCheckboxMenuItem)menu_item).setState(state); + else + ((CheckboxMenuItem)menu_item).setState(state); + } + + /** + * Get the state of a ArtemisCheckboxMenuItem or a CheckboxMenuItem. + **/ + private boolean getCheckboxMenuItemState(final MenuItem menu_item) + { + if(menu_item instanceof ArtemisCheckboxMenuItem) + return((ArtemisCheckboxMenuItem)menu_item).getState(); + else + return((CheckboxMenuItem)menu_item).getState(); + } + +} diff --git a/uk/ac/sanger/artemis/components/FeatureViewer.java b/uk/ac/sanger/artemis/components/FeatureViewer.java new file mode 100644 index 000000000..3a4afd0db --- /dev/null +++ b/uk/ac/sanger/artemis/components/FeatureViewer.java @@ -0,0 +1,151 @@ +/* FeatureViewer.java + * + * created: Thu Nov 19 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureViewer.java,v 1.1 2004-06-09 09:46:46 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.io.*; +import java.awt.event.*; + +/** + * A viewer for Feature objects. + * + * @author Kim Rutherford + * @version $Id: FeatureViewer.java,v 1.1 2004-06-09 09:46:46 tjc Exp $ + * + **/ + +public class FeatureViewer + implements EntryChangeListener, FeatureChangeListener { + /** + * Create a new FeatureViewer object from the given Feature. + **/ + public FeatureViewer (Feature view_feature) { + this.view_feature = view_feature; + this.entry = view_feature.getEntry (); + + file_viewer = new FileViewer ("Artemis Feature View: " + + view_feature.getIDString ()); + readFeature (view_feature); + + view_feature.getEntry ().addEntryChangeListener (this); + view_feature.addFeatureChangeListener (this); + + file_viewer.addWindowListener (new WindowAdapter () { + public void windowClosed (WindowEvent event) { + stopListening (); + } + }); + } + + /** + * Remove this object as a feature and entry change listener. + **/ + public void stopListening () { + getEntry ().removeEntryChangeListener (this); + getFeature ().removeFeatureChangeListener (this); + } + + /** + * Implementation of the EntryChangeListener interface. We listen to + * EntryChange events so we can delete this component if the feature gets + * deleted. + **/ + public void entryChanged (EntryChangeEvent event) { + switch (event.getType ()) { + case EntryChangeEvent.FEATURE_DELETED: + if (event.getFeature () == view_feature) { + stopListening (); + file_viewer.dispose (); + } + break; + default: + // do nothing; + break; + } + } + + /** + * Implementation of the FeatureChangeListener interface. We need to + * listen to feature change events from the Features in this object so that + * we can keep the display up to date. + * @param event The change event. + **/ + public void featureChanged (FeatureChangeEvent event) { + // re-read the information from the feature + readFeature (view_feature); + } + + /** + * Read the given Feature into this FeatureViewer object. + **/ + public void readFeature (Feature feature) { + try { + file_viewer.clear (); + file_viewer.appendFile (view_feature.toReader ()); + } catch (uk.ac.sanger.artemis.io.ReadFormatException e) { + throw new Error ("internal error - unexpected exception: " + + e.getMessage () + + (e.getLineNumber () > 1 ? + " at line " + e.getLineNumber () : + "")); + } catch (IOException e) { + throw new Error ("internal error - unexpected exception: " + + e.getMessage ()); + } + } + + /** + * Return the Feature we are viewing. + **/ + public Feature getFeature () { + return view_feature; + } + + /** + * Return the Entry that contains the Feature this object is displaying. + **/ + private Entry getEntry () { + return entry; + } + + /** + * The Feature this object is displaying. + **/ + private Feature view_feature; + + /** + * The Entry that contains the Feature this object is displaying. + **/ + private Entry entry; + + /** + * The FileViewer object that is displaying the feature. + **/ + private FileViewer file_viewer; +} + + diff --git a/uk/ac/sanger/artemis/components/FileDialogEntrySource.java b/uk/ac/sanger/artemis/components/FileDialogEntrySource.java new file mode 100644 index 000000000..1a8ea9fd8 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FileDialogEntrySource.java @@ -0,0 +1,286 @@ +/* FileDialogEntrySource.java + * + * created: Thu Jun 8 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FileDialogEntrySource.java,v 1.1 2004-06-09 09:46:49 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; + +import javax.swing.JFrame; + +/** + * This is an EntrySource that reads Entry objects from the local filesystem. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: FileDialogEntrySource.java,v 1.1 2004-06-09 09:46:49 tjc Exp $ + **/ + +public class FileDialogEntrySource + implements EntrySource +{ + + /** + * The component that created this FileEntrySource. (Used for requesters.) + **/ + final JFrame frame; + + /** InputStreamProgressEvents are sent to this object. */ + private final InputStreamProgressListener stream_progress_listener; + + /** + * Create a new FileDialogEntrySource. + * @param frame The component that created this FileDialogEntrySource. + * (Used for requesters.) + * @param listener InputStreamProgressEvent objects will be sent to this + * listener as progress on reading is made. + **/ + public FileDialogEntrySource (final JFrame frame, + final InputStreamProgressListener listener) + { + this.frame = frame; + this.stream_progress_listener = listener; + } + + /** + * Get an Entry object from this source (by reading from a file). + * @param bases The Bases object to pass to the Entry constructor. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + * @return null if and only if the user cancels the read. + **/ + public Entry getEntry(final Bases bases, + final ProgressThread progress_thread, + final boolean show_progress) + throws OutOfRangeException + { + try + { + return getEntryInternal(bases, progress_thread, show_progress); + } + catch (NoSequenceException e) + { + throw new Error ("internal error - unexpected exception: " + e); + } + } + + + /** + * Get an Entry object from this source (by reading from a file). + * @param bases The Bases object to pass to the Entry constructor. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + * @return null if and only if the user cancels the read. + **/ + public Entry getEntry (final Bases bases, + final boolean show_progress) + throws OutOfRangeException + { + try + { + return getEntryInternal (bases, null, show_progress); + } + catch (NoSequenceException e) + { + throw new Error ("internal error - unexpected exception: " + e); + } + } + + /** + * Get an Entry object from this source (by reading from a file). + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + * @return null if and only if the user cancels the read. + **/ + public Entry getEntry (final boolean show_progress) + throws OutOfRangeException, NoSequenceException + { + return getEntryInternal (null, null, show_progress); + } + + /** + * Get an Entry object from this source (by reading from a file). + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + * @return null if and only if the user cancels the read. + **/ + public Entry getEntry (final boolean show_progress, + final ProgressThread progress_thread) + throws OutOfRangeException, NoSequenceException + { + return getEntryInternal (null, progress_thread, show_progress); + } + + + /** + * Returns true if and only if this EntrySource always returns "full" + * entries. ie. entries that contain features and sequence. Entries that + * are read from a file may contain just features so in this class this + * method returns false. + **/ + public boolean isFullEntrySource () + { + return false; + } + + +//// change ArtemisMain.readArgsAndOptions () to use this?: + +// /** +// * Get an Entry object from this source by name (by reading from a file). +// * @exception OutOfRangeException Thrown if one of the features in +// * embl_entry is out of range of the Bases object. +// * @exception NoSequenceException Thrown if the entry that we read has no +// * sequence. +// * @return null if and only if there is no Entry with that name. +// **/ +// public Entry getEntryByName (final String entry_file_name) +// throws OutOfRangeException, NoSequenceException, IOException { +// final Document new_document = +// new FileProgressDocument (new File (entry_file_name), +// getInputStreamProgressListener ()); + +// final EntryInformation new_entry_information = +// new SimpleEntryInformation (Options.getArtemisEntryInformation ()); + +// boolean seen_error = false; + +// while (true) { +// try { +// final uk.ac.sanger.artemis.io.Entry new_entry = +// DocumentEntryFactory.makeDocumentEntry (new_entry_information, +// new_document); + +// return new Entry (new_entry); +// } catch (EntryInformationException e) { + +// if (!seen_error) { +// final String message = +// "warning while reading " + entry_file_name + " - " + +// e.getMessage (); + +// System.err.println (message); + +// new MessageDialog (frame, message); + +// seen_error = true; +// } + +// EntryFileDialog.handleOpenException (new_entry_information, e); + +// // go around the loop again +// } +// } +// } + + /** + * Make a new Entry. + * @param bases The Bases object to pass to the Entry constructor. If null + * a new Bases object will be created. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if bases is null and the Entry that + * we read has no sequence. + **/ + private Entry makeEntry (final Bases bases, + final uk.ac.sanger.artemis.io.Entry embl_entry) + throws OutOfRangeException, NoSequenceException + { + if (bases == null) + return new Entry (embl_entry); + else + return new Entry (bases, embl_entry); + } + + /** + * Return the InputStreamProgressListener that was passed to the + * constructor. + **/ + public InputStreamProgressListener getInputStreamProgressListener () + { + return stream_progress_listener; + } + + /** + * Return the name of this source (for display to the user in menus). + **/ + public String getSourceName () + { + return "Filesystem"; + } + + /** + * Implementation of getEntry (). + * @param bases The Bases object to pass to the Entry constructor. + * @exception OutOfRangeException Thrown if one of the features in + * Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if bases is null and the entry that + * we read has no sequence. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + * @return null if and only if the user cancels the read. + **/ + private Entry getEntryInternal(final Bases bases, + final ProgressThread progress_thread, + final boolean show_progress) + throws OutOfRangeException, NoSequenceException + { + final EntryInformation new_entry_information = + new SimpleEntryInformation(Options.getArtemisEntryInformation()); + + EntryFileDialog dialog; + + if(bases == null) + dialog = new EntryFileDialog(frame, true); + else + dialog = new EntryFileDialog(frame, false); + + + uk.ac.sanger.artemis.io.Entry new_embl_entry = + dialog.getEntry(new_entry_information, stream_progress_listener, + progress_thread, show_progress); + + if(new_embl_entry == null) + return null; + + return makeEntry(bases, new_embl_entry); + } + +} + diff --git a/uk/ac/sanger/artemis/components/FileManager.java b/uk/ac/sanger/artemis/components/FileManager.java new file mode 100644 index 000000000..54047d835 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FileManager.java @@ -0,0 +1,401 @@ +/******************************************************************** +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library 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 +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +* +* Copyright (C) Genome Research Limited +* +********************************************************************/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.Options; + +import javax.swing.*; +import java.io.File; +import java.io.FileFilter; +import java.awt.event.*; +import java.awt.geom.*; +import java.awt.*; + +public class FileManager extends JFrame +{ + + /** busy cursor */ + private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR); + /** done cursor */ + private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR); + + public FileManager(JFrame frame) + { + this(frame,getArtemisFilter()); + } + + /** + * + * File Manager Frame + * @param frame parent frame + * + */ + public FileManager(JFrame frame, FileFilter filter) + { + super("File Manager"); + + FileTree ftree = new FileTree(new File(System.getProperty("user.dir")), + frame, filter); + JScrollPane jsp = new JScrollPane(ftree); + JPanel pane = (JPanel)getContentPane(); + pane.setLayout(new BorderLayout()); + pane.add(jsp, BorderLayout.CENTER); + setJMenuBar(makeMenuBar(pane,ftree)); + pane.add(getFileFileterComboBox(ftree), BorderLayout.SOUTH); + + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + jsp.setPreferredSize(new Dimension(210, + (int)(screen.getHeight()/2))); + pack(); + + int yloc = (int)((screen.getHeight()-getHeight())/2); + setLocation(0,yloc); + setVisible(true); + } + + protected JComboBox getFileFileterComboBox(final FileTree ftree) + { + String[] filters = { "Artemis Files", "Sequence Files", + "Feature Files", "All Files" }; + final JComboBox comboFilter = new JComboBox(filters); + comboFilter.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + String select = (String)comboFilter.getSelectedItem(); + if(select.equals("Artemis Files")) + ftree.setFilter(getArtemisFilter()); + else if(select.equals("Sequence Files")) + ftree.setFilter(getSequenceFilter()); + else if(select.equals("Feature Files")) + ftree.setFilter(getFeatureFilter()); + else if(select.equals("All Files")) + { + ftree.setFilter(new FileFilter() + { + public boolean accept(File pathname) + { + if(pathname.getName().startsWith(".")) + return false; + return true; + } + }); + } + } + }); + return comboFilter; + } + + /** + * + * Get a file filter for sequence and feature suffixes. + * @return file filter + */ + protected static FileFilter getArtemisFilter() + { + final StringVector sequence_suffixes = + Options.getOptions().getOptionValues("sequence_file_suffixes"); + + final StringVector feature_suffixes = + Options.getOptions().getOptionValues("feature_file_suffixes"); + + final FileFilter artemis_filter = new FileFilter() + { + public boolean accept(File pathname) + { + if(pathname.isDirectory() && + !pathname.getName().startsWith(".")) + return true; + + for(int i = 0; i<sequence_suffixes.size(); ++i) + { + final String suffix = sequence_suffixes.elementAt(i); + + if(pathname.getName().endsWith("." + suffix) || + pathname.getName().endsWith("." + suffix + ".gz")) + return true; + } + + for(int i = 0; i<feature_suffixes.size(); ++i) + { + final String suffix = feature_suffixes.elementAt(i); + + if(pathname.getName().endsWith("." + suffix) || + pathname.getName().endsWith("." + suffix + ".gz")) + return true; + } + return false; + } + }; + return artemis_filter; + } + + + /** + * + * Get a file filter for feature suffixes. + * @return file filter + */ + protected static FileFilter getFeatureFilter() + { + final StringVector feature_suffixes = + Options.getOptions().getOptionValues("feature_file_suffixes"); + + final FileFilter feature_filter = new FileFilter() + { + public boolean accept(File pathname) + { + if(pathname.isDirectory() && + !pathname.getName().startsWith(".")) + return true; + + for(int i = 0; i<feature_suffixes.size(); ++i) + { + final String suffix = feature_suffixes.elementAt(i); + + if(pathname.getName().endsWith("." + suffix) || + pathname.getName().endsWith("." + suffix + ".gz")) + return true; + } + return false; + } + }; + return feature_filter; + } + + /** + * + * Get a file filter for sequence suffixes. + * @return file filter + */ + protected static FileFilter getSequenceFilter() + { + final StringVector sequence_suffixes = + Options.getOptions().getOptionValues("sequence_file_suffixes"); + + final FileFilter seq_filter = new FileFilter() + { + public boolean accept(File pathname) + { + if(pathname.isDirectory() && + !pathname.getName().startsWith(".")) + return true; + + for(int i = 0; i<sequence_suffixes.size(); ++i) + { + final String suffix = sequence_suffixes.elementAt(i); + + if(pathname.getName().endsWith("." + suffix) || + pathname.getName().endsWith("." + suffix + ".gz")) + return true; + } + + return false; + } + }; + return seq_filter; + } + + /** + * + * Set up a menu and tool bar + * @param pane panel to add toolbar to + * @param ftree file tree display + * + */ + private JMenuBar makeMenuBar(JPanel pane, final FileTree ftree) + { + JMenuBar mBar = new JMenuBar(); + JMenu fileMenu = new JMenu("File"); + mBar.add(fileMenu); + + JMenuItem fileMenuGoto = new JMenuItem("Go to Directory ..."); + fileMenuGoto.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + String dir = ftree.getRoot().getAbsolutePath(); + String newDir = JOptionPane.showInputDialog(FileManager.this, + "Go to Directory:",dir); + + if(newDir == null) + return; + + newDir = newDir.trim(); + File newDirFile = new File(newDir); + + if(newDirFile.exists() && + newDirFile.canRead() && + !newDir.equals(dir)) + ftree.newRoot(newDir); + else + { + String error = null; + if(!newDirFile.exists()) + error = new String(newDir+" doesn't exist!"); + else if(!newDirFile.canRead()) + error = new String(newDir+" cannot be read!"); + else if(newDir.equals(dir)) + error = new String("Same directory!"); + + if(error != null) + JOptionPane.showMessageDialog(FileManager.this, + error, "Warning", + JOptionPane.WARNING_MESSAGE); + } + } + }); + fileMenu.add(fileMenuGoto); + fileMenu.add(new JSeparator()); + + JMenuItem fileMenuClose = new JMenuItem("Close"); + fileMenuClose.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + setVisible(false); + } + }); + fileMenu.add(fileMenuClose); + + // tool bar set up + JToolBar toolBar = new JToolBar(); + Dimension buttonSize = new Dimension(22,24); + + JButton upBt = new JButton() + { + public void paintComponent(Graphics g) + { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D)g; + + g2.setColor(new Color(0,128,0)); + float loc1[][] = { {11,18}, {7,18}, {7,14}, + {3,14}, {11,4} }; + + g2.fill(makeShape(loc1)); + g2.setColor(Color.green); + + float loc2[][] = { {11,18}, {15,18}, {15,14}, + {19,14}, {11,4} }; + g2.fill(makeShape(loc2)); + + setSize(22,24); + } + }; + upBt.setPreferredSize(buttonSize); + upBt.setMinimumSize(buttonSize); + + upBt.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + FileManager.this.setCursor(cbusy); + File root = ftree.getRoot(); + String parent = root.getParent(); + if(parent != null) + ftree.newRoot(parent); + FileManager.this.setCursor(cdone); + } + }); + toolBar.add(upBt); + + JButton shortCut1 = new JButton() + { + public void paintComponent(Graphics g) + { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D)g; + Font font = new Font("Monospaced", Font.BOLD, 14); + g2.setFont(font); + + g2.setColor(Color.black); + g2.drawString("Y",4,18); + g2.setColor(Color.red); + g2.drawString("P",10,15); + setSize(22,24); + } + }; + shortCut1.setPreferredSize(buttonSize); + shortCut1.setMinimumSize(buttonSize); + shortCut1.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + ftree.newRoot("/nfs/disk222/yeastpub"); + } + }); + toolBar.add(shortCut1); + + JButton homeBt = new JButton() + { + public void paintComponent(Graphics g) + { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D)g; + + g2.setColor(Color.gray); + float loc1[][] = { {3,14}, {11,3}, {19,14}, + {17,14}, {17,18}, {5,18}, {5,14} }; + g2.fill(makeShape(loc1)); + + setSize(22,24); + } + }; + homeBt.setPreferredSize(buttonSize); + homeBt.setMinimumSize(buttonSize); + homeBt.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + ftree.newRoot(System.getProperty("user.home")); + } + }); + toolBar.add(homeBt); + + toolBar.add(Box.createVerticalStrut(35)); + pane.add(toolBar, BorderLayout.NORTH); + + return mBar; + } + + /** + * + * Used to draw a Shape. + * + */ + public static GeneralPath makeShape(float loc[][]) + { + GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO); + + path.moveTo(loc[0][0],loc[0][1]); + + for(int i=1; i<loc.length; i++) + path.lineTo(loc[i][0],loc[i][1]); + + return path; + } + + +} + diff --git a/uk/ac/sanger/artemis/components/FileNode.java b/uk/ac/sanger/artemis/components/FileNode.java new file mode 100644 index 000000000..084ef31b7 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FileNode.java @@ -0,0 +1,159 @@ +/******************************************************************** +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library 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 +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +* +* Copyright (C) Genome Research Limited +* +********************************************************************/ + +package uk.ac.sanger.artemis.components; + +import java.awt.datatransfer.*; +import javax.swing.tree.*; +import java.io.*; +import java.util.*; + +/** +* +* File node for local file tree manager +* +*/ +public class FileNode extends DefaultMutableTreeNode + implements Transferable, Serializable +{ + /** true if explored */ + private boolean explored = false; + /** data flavour of a file node */ + public static DataFlavor FILENODE = + new DataFlavor(FileNode.class, "Local file"); + /** flavours file node and string */ + static DataFlavor flavors[] = { FILENODE, DataFlavor.stringFlavor }; + + /** + * + * @param file file node file + * + */ + public FileNode(File file) + { + setUserObject(file); + } + + /** Determine if this is a directory */ + public boolean getAllowsChildren() { return isDirectory(); } + /** Determine if this is a file */ + public boolean isLeaf() { return !isDirectory(); } + /** Get the File this node represents */ + public File getFile() { return (File)getUserObject(); } + /** Determine if this node has been explored */ + public boolean isExplored() { return explored; } + /** Determine if this is a directory */ + public boolean isDirectory() + { + File file = getFile(); + return file.isDirectory(); + } + + /** + * + * Returns the name of the file + * + */ + public String toString() + { + File file = (File)getUserObject(); + String filename = file.toString(); + int index = filename.lastIndexOf(File.separator); + + return (index != -1 && index != filename.length()-1) ? + filename.substring(index+1) : + filename; + } + + /** + * + * Explores a directory adding a FileNode for each + * child + * + */ + public void explore(FileFilter filter) + { + if(!isDirectory()) + return; + + if(!isExplored()) + { + File file = getFile(); + explored = true; + File[] children; +// filter files + children = file.listFiles(filter); + +// sort into alphabetic order + java.util.Arrays.sort(children); + for(int i=0; i < children.length; ++i) + add(new FileNode(children[i])); + } + } + + /** + * + * Forces the directory to be re-explored + * + */ + public void reExplore(FileFilter filter) + { + explored = false; + removeAllChildren(); + explore(filter); + } + +// Transferable + public DataFlavor[] getTransferDataFlavors() + { + return flavors; + } + + public boolean isDataFlavorSupported(DataFlavor f) + { + if(f.equals(FILENODE) || f.equals(DataFlavor.stringFlavor)) + return true; + return false; + } + + public Object getTransferData(DataFlavor d) + throws UnsupportedFlavorException, IOException + { + if(d.equals(FILENODE)) + return this; + else if(d.equals(DataFlavor.stringFlavor)) + return getFile().getAbsolutePath(); + else throw new UnsupportedFlavorException(d); + } + +//Serializable + private void writeObject(java.io.ObjectOutputStream out) throws IOException + { + out.defaultWriteObject(); + } + + private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + } + +} diff --git a/uk/ac/sanger/artemis/components/FileTree.java b/uk/ac/sanger/artemis/components/FileTree.java new file mode 100644 index 000000000..34c646a19 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FileTree.java @@ -0,0 +1,941 @@ +/******************************************************************** +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library 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 +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +* +* Copyright (C) Genome Research Limited +* +********************************************************************/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.util.FileDocument; +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.sequence.NoSequenceException; +import uk.ac.sanger.artemis.components.MessageDialog; + +import java.awt.*; +import java.awt.event.*; +import java.awt.datatransfer.*; +import java.awt.dnd.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.tree.*; +import java.io.*; +import java.util.Vector; +import java.util.Enumeration; +import java.util.Hashtable; + + +/** +* +* Creates a local file tree manager. This acts as a drag +* source and sink for files. +* +*/ +public class FileTree extends JTree implements DragGestureListener, + DragSourceListener, DropTargetListener, ActionListener, + Autoscroll +{ + + /** root directory */ + private File root; + /** store of directories that are opened */ + private Vector openNode; + /** file separator */ + private String fs = new String(System.getProperty("file.separator")); + /** popup menu */ + private JPopupMenu popup; + /** busy cursor */ + private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR); + /** done cursor */ + private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR); + /** AutoScroll margin */ + private static final int AUTOSCROLL_MARGIN = 45; + /** used by AutoScroll method */ + private Insets autoscrollInsets = new Insets( 0, 0, 0, 0 ); + /** file filter */ + private FileFilter filter = null; + + /** + * + * @param rt root directory + * @param f frame + * + */ + public FileTree(File rt, final JFrame f, + FileFilter filter) + { + this.root = rt; + this.filter = filter; + + DragSource dragSource = DragSource.getDefaultDragSource(); + + dragSource.createDefaultDragGestureRecognizer( + this, // component where drag originates + DnDConstants.ACTION_COPY_OR_MOVE, // actions + this); // drag gesture recognizer + + setDropTarget(new DropTarget(this,this)); + DefaultTreeModel model = createTreeModel(root); + setModel(model); + createTreeModelListener(); + + this.getSelectionModel().setSelectionMode + (TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + + // Popup menu + addMouseListener(new PopupListener()); + popup = new JPopupMenu(); + + JMenuItem menuItem = new JMenuItem("Refresh"); + menuItem.addActionListener(this); + popup.add(menuItem); + popup.add(new JSeparator()); + //open menu + JMenu openMenu = new JMenu("Open With"); + popup.add(openMenu); + menuItem = new JMenuItem("Jemboss Alignment Editor"); + menuItem.addActionListener(this); + openMenu.add(menuItem); + menuItem = new JMenuItem("Artemis"); + menuItem.addActionListener(this); + openMenu.add(menuItem); + + menuItem = new JMenuItem("Rename..."); + menuItem.addActionListener(this); + popup.add(menuItem); + menuItem = new JMenuItem("New Folder..."); + menuItem.addActionListener(this); + popup.add(menuItem); + menuItem = new JMenuItem("Delete..."); + menuItem.addActionListener(this); + popup.add(menuItem); + popup.add(new JSeparator()); + menuItem = new JMenuItem("De-select All"); + menuItem.addActionListener(this); + popup.add(menuItem); + + //Listen for when a file is selected + MouseListener mouseListener = new MouseAdapter() + { + public void mouseClicked(MouseEvent me) + { + if(me.getClickCount() == 2 && isFileSelection() && + !me.isPopupTrigger()) + { + f.setCursor(cbusy); + FileNode node = getSelectedNode(); + String selected = node.getFile().getAbsolutePath(); + showFilePane(selected); + f.setCursor(cdone); + } + } + }; + this.addMouseListener(mouseListener); + + addTreeExpansionListener(new TreeExpansionListener() + { + public void treeCollapsed(TreeExpansionEvent e){} + public void treeExpanded(TreeExpansionEvent e) + { + TreePath path = e.getPath(); + if(path != null) + { + FileNode node = (FileNode)path.getLastPathComponent(); + + if(!node.isExplored()) + { + f.setCursor(cbusy); + exploreNode(node); + f.setCursor(cdone); + } + } + } + }); + + } + + + /** + * + * Popup menu actions + * @param e action event + * + */ + public void actionPerformed(ActionEvent e) + { + + JMenuItem source = (JMenuItem)(e.getSource()); + final FileNode node = getSelectedNode(); + + if(source.getText().equals("Refresh")) + { + if(node == null) + newRoot(root.getAbsolutePath()); + else if(node.isLeaf()) + refresh((FileNode)node.getParent()); + else + refresh(node); + return; + } + + if(node == null) + { + JOptionPane.showMessageDialog(null,"No file selected.", + "Warning", + JOptionPane.WARNING_MESSAGE); + return; + } + + final File f = node.getFile(); + + if(source.getText().equals("Jemboss Alignment Editor")) + { +// org.emboss.jemboss.editor.AlignJFrame ajFrame = +// new org.emboss.jemboss.editor.AlignJFrame(f); +// ajFrame.setVisible(true); + } + else if(source.getText().equals("Text Editor")) + showFilePane(f.getAbsolutePath()); + else if(source.getText().equals("New Folder...")) + { + String path = null; + if(node.isLeaf()) + path = f.getParent(); + else + path = f.getAbsolutePath(); + + String inputValue = JOptionPane.showInputDialog(null, + "Folder Name","Create New Folder in "+path, + JOptionPane.QUESTION_MESSAGE); + + if(inputValue != null && !inputValue.equals("") ) + { + String fullname = path+fs+inputValue; + File dir = new File(fullname); + + if(dir.exists()) + JOptionPane.showMessageDialog(null, fullname+" alread exists!", + "Error", JOptionPane.ERROR_MESSAGE); + else + { + if(dir.mkdir()) + addObject(inputValue,path,node); + else + JOptionPane.showMessageDialog(null, + "Cannot make the folder\n"+fullname, + "Error", JOptionPane.ERROR_MESSAGE); + } + } + } + else if(source.getText().equals("Delete...")) + { + File fn[] = getSelectedFiles(); + String names = ""; + for(int i=0; i<fn.length;i++) + names = names.concat(fn[i].getAbsolutePath()+"\n"); + int n = JOptionPane.showConfirmDialog(null, + "Delete \n"+names+"?", + "Delete Files", + JOptionPane.YES_NO_OPTION); + + FileNode nodes[] = getSelectedNodes(); + if(n == JOptionPane.YES_OPTION) + for(int i=0; i<nodes.length;i++) + deleteFile(nodes[i]); + } + else if(source.getText().equals("De-select All")) + clearSelection(); + else if(isFileSelection() && source.getText().equals("Rename...")) + { + String inputValue = (String)JOptionPane.showInputDialog(null, + "New File Name","Rename "+f.getName(), + JOptionPane.QUESTION_MESSAGE,null,null,f.getName()); + + if(inputValue != null && !inputValue.equals("") ) + { + String path = f.getParent(); + String fullname = path+fs+inputValue; + File newFile = new File(fullname); + + try + { + renameFile(f,node,newFile.getCanonicalPath()); + } + catch(IOException ioe){} + } + } + } + + + /** + * + * Delete a file from the tree + * @param node node to delete + * + */ + public void deleteFile(final FileNode node) + { + File f = node.getFile(); + if(f.delete()) + { + Runnable deleteFileFromTree = new Runnable() + { + public void run () { deleteObject(node); }; + }; + SwingUtilities.invokeLater(deleteFileFromTree); + } + else + JOptionPane.showMessageDialog(null,"Cannot delete\n"+ + f.getAbsolutePath(),"Warning", + JOptionPane.ERROR_MESSAGE); + } + + + /** + * + * Method to rename a file and update the filenode's. + * @param oldFile file to rename + * @param oldNode filenode to be removed + * @param newFullName name of the new file + * + */ + private void renameFile(final File oldFile, final FileNode oldNode, + String newFullName) + { + final File fnew = new File(newFullName); + if(fnew.exists()) + JOptionPane.showMessageDialog(null, newFullName+" alread exists!", + "Warning", JOptionPane.ERROR_MESSAGE); + else + { + if(oldFile.renameTo(fnew)) + { + Runnable renameFileInTree = new Runnable() + { + public void run () + { + addObject(fnew.getName(),fnew.getParent(),oldNode); + deleteObject(oldNode); + }; + }; + SwingUtilities.invokeLater(renameFileInTree); + } + else + JOptionPane.showMessageDialog(null, + "Cannot rename \n"+oldFile.getAbsolutePath()+ + "\nto\n"+fnew.getAbsolutePath(), "Rename Error", + JOptionPane.ERROR_MESSAGE); + } + return; + } + + + /** + * + * Define a directory root for the file tree + * @param newRoot directory to use as the root for + * the tree. + * + */ + public void newRoot(String newRoot) + { + root = new File(newRoot); + DefaultTreeModel model = (DefaultTreeModel)getModel(); + model = createTreeModel(root); + setModel(model); + } + + /** + * + * Get the current root node. + * @return directory root. + * + */ + public File getRoot() + { + return root; + } + + /** + * + * Refresh + * @param FileNode node to refresh + * + */ + public void refresh(FileNode node) + { + node.reExplore(filter); + DefaultTreeModel model = (DefaultTreeModel)getModel(); + model.nodeStructureChanged(node); + } + + /** + * + * Set the current file filter + * + */ + public void setFilter(FileFilter filter) + { + this.filter = filter; + Enumeration en = openNode.elements(); + + while(en.hasMoreElements()) + refresh((FileNode)en.nextElement()); + } + + /** + * + * Get FileNode of selected node + * @return node that is currently selected + * + */ + public FileNode getSelectedNode() + { + TreePath path = getLeadSelectionPath(); + if(path == null) + return null; + FileNode node = (FileNode)path.getLastPathComponent(); + return node; + } + + + /** + * + * Get FileNodes of selected nodes + * @return node that is currently selected + * + */ + public FileNode[] getSelectedNodes() + { + TreePath path[] = getSelectionPaths(); + if(path == null) + return null; + + int numberSelected = path.length; + FileNode nodes[] = new FileNode[numberSelected]; + for(int i=0;i<numberSelected;i++) + nodes[i] = (FileNode)path[i].getLastPathComponent(); + + return nodes; + } + + + /** + * + * Get selected files + * @return node that is currently selected + * + */ + public File[] getSelectedFiles() + { + FileNode[] fn = getSelectedNodes(); + int numberSelected = fn.length; + File files[] = new File[numberSelected]; + for(int i=0;i<numberSelected;i++) + files[i] = fn[i].getFile(); + + return files; + } + + + /** + * + * Return true if selected node is a file + * @return true is a file is selected, false if + * a directory is selected + * + */ + public boolean isFileSelection() + { + TreePath path = getLeadSelectionPath(); + if(path == null) + return false; + + FileNode node = (FileNode)path.getLastPathComponent(); + return node.isLeaf(); + } + + + /** + * + * Make the given directory the root and create a new + * DefaultTreeModel. + * @param root root directory + * @param tree model with the root node set + * to the given directory + * + */ + private DefaultTreeModel createTreeModel(File root) + { + FileNode rootNode = new FileNode(root); + rootNode.explore(filter); + openNode = new Vector(); + openNode.add(rootNode); + return new DefaultTreeModel(rootNode); + } + + + /** + * + * Adding a file (or directory) to the file tree manager. + * This looks to see if the directory has already been opened + * and updates the filetree if it has. + * @param child new child to add in + * @param path path to where child is to be added + * @param node node to add child to + * + */ + public DefaultMutableTreeNode addObject(String child, + String path, FileNode node) + { + + DefaultTreeModel model = (DefaultTreeModel)getModel(); + if(node == null) + { + node = getNode(path); + if(node==null) + return null; + } + + FileNode parentNode = getNode(path); + File newleaf = new File(path + fs + child); + FileNode childNode = null; + + if(parentNode.isExplored()) + { + childNode = new FileNode(newleaf); + int index = getAnIndex(parentNode,child); + if(index > -1) + model.insertNodeInto(childNode, parentNode, index); + } + else if(parentNode.isDirectory()) + { + exploreNode(parentNode); + childNode = getNode(path + fs + child); + } + + // Make sure the user can see the new node. + this.scrollPathToVisible(new TreePath(childNode.getPath())); + return childNode; + } + + + /** + * + * Delete a node from the JTree + * @param node node for deletion + * + */ + public void deleteObject(FileNode node) + { + DefaultTreeModel model =(DefaultTreeModel)getModel(); + FileNode parentNode = getNode(node.getFile().getParent()); + model.removeNodeFromParent(node); + } + + + /** + * + * Explore a directory node + * @param dirNode direcory node to display + * + */ + public void exploreNode(FileNode dirNode) + { + DefaultTreeModel model = (DefaultTreeModel)getModel(); + dirNode.explore(filter); + openNode.add(dirNode); + model.nodeStructureChanged(dirNode); + } + + /** + * + * Gets the node from the existing explored nodes and their + * children. + * @param path path to a file or directory + * @return corresponding node if the directory or + * file is visible otherwise returns null. + * + */ + private FileNode getNode(String path) + { + Enumeration en = openNode.elements(); + + while(en.hasMoreElements()) + { + FileNode node = (FileNode)en.nextElement(); + String nodeName = node.getFile().getAbsolutePath(); + if(nodeName.equals(path)) + return node; + } + +// check children of explored nodes + en = openNode.elements(); + while(en.hasMoreElements()) + { + FileNode child = getChildNode((FileNode)en.nextElement(),path); + if(child != null) + return child; + } + + return null; + } + + + /** + * + * Gets the child node of a parent node + * @param parent parent node + * @param childName name of child + * @return the child node + * + */ + private FileNode getChildNode(FileNode parent, String childName) + { + for(Enumeration children = parent.children(); children.hasMoreElements() ;) + { + FileNode childNode = (FileNode)children.nextElement(); + String nodeName = childNode.getFile().getAbsolutePath(); + if(childName.equals(nodeName)) + return childNode; + } + + return null; + } + + /** + * + * Finds a new index for adding a new file to the file manager. + * @param parentNode parent directory node + * @param child new child node + * @return index of the child in the directory + * + */ + private int getAnIndex(FileNode parentNode, String child) + { + //find the index for the child + int num = parentNode.getChildCount(); + int childIndex = num; + for(int i=0;i<num;i++) + { + String nodeName = + ((FileNode)parentNode.getChildAt(i)).getFile().getName(); + if(nodeName.compareTo(child) > 0) + { + childIndex = i; + break; + } + else if (nodeName.compareTo(child) == 0) //file already exists + { + childIndex = -1; + break; + } + } + return childIndex; + } + + + /** + * + * Read a file into a byte array. + * @param filename file name + * @return byte[] contents of file + * + */ + protected static byte[] readByteFile(String filename) + { + + File fn = new File(filename); + byte[] b = null; + try + { + long s = fn.length(); + if(s == 0) + return b; + b = new byte[(int)s]; + FileInputStream fi = new FileInputStream(fn); + fi.read(b); + fi.close(); + } + catch (IOException ioe) + { + System.out.println("Cannot read file: " + filename); + } + return b; + + } + + /** + * + * Opens a JFrame with the file contents displayed. + * @param filename file name to display + * + */ + public void showFilePane(final String filename) + { + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + + progress_thread.start(); + SwingWorker entryWorker = new SwingWorker() + { + EntryEdit entry_edit; + public Object construct() + { + try + { + EntryInformation new_entry_information = + new SimpleEntryInformation(Options.getArtemisEntryInformation()); + + final Entry entry = new Entry(EntryFileDialog.getEntryFromFile( + null, new FileDocument(new File(filename)), + new_entry_information, false)); + if(entry == null) + return null; + + final EntryGroup entry_group = + new SimpleEntryGroup(entry.getBases()); + + entry_group.add(entry); + entry_edit = new EntryEdit(entry_group); + return null; + } + catch(NoSequenceException e) + { + new MessageDialog(null, "read failed: entry contains no sequence"); + } + catch(OutOfRangeException e) + { + new MessageDialog(null, "read failed: one of the features in " + + " the entry has an out of range " + + "location: " + e.getMessage()); + + } + catch(NullPointerException npe){} + + return null; + } + + public void finished() + { + if(entry_edit != null) + entry_edit.setVisible(true); + if(progress_thread !=null) + progress_thread.finished(); + } + }; + entryWorker.start(); + + } + + +//////////////////// +// DRAG AND DROP +//////////////////// +// drag source + public void dragGestureRecognized(DragGestureEvent e) + { + // ignore if mouse popup trigger + InputEvent ie = e.getTriggerEvent(); + if(ie instanceof MouseEvent) + if(((MouseEvent)ie).isPopupTrigger()) + return; + + // drag only files + if(isFileSelection()) + e.startDrag(DragSource.DefaultCopyDrop, // cursor + (Transferable)getSelectedNode(), // transferable data + this); // drag source listener + } + public void dragDropEnd(DragSourceDropEvent e) {} + public void dragEnter(DragSourceDragEvent e) {} + public void dragExit(DragSourceEvent e) {} + public void dragOver(DragSourceDragEvent e) {} + public void dropActionChanged(DragSourceDragEvent e) {} + +// drop sink + public void dragEnter(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(FileNode.FILENODE)) + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + } + + public void drop(DropTargetDropEvent e) + { + + Transferable t = e.getTransferable(); + final FileNode dropNode = getSelectedNode(); + if(dropNode == null) + { + e.rejectDrop(); + return; + } + + //local drop + if(t.isDataFlavorSupported(FileNode.FILENODE)) + { + try + { + FileNode fn = (FileNode)t.getTransferData(FileNode.FILENODE); + fn = getNode(fn.getFile().getAbsolutePath()); + + if (dropNode.isLeaf()) + { + e.rejectDrop(); + return; + } + + String dropDir = dropNode.getFile().getAbsolutePath(); + String newFullName = dropDir+fs+fn.toString(); + renameFile(fn.getFile(),fn,newFullName); + } + catch(Exception ufe){} + } + else + { + e.rejectDrop(); + return; + } + + } + + + /** + * + * When a suitable DataFlavor is offered over a remote file + * node the node is highlighted/selected and the drag + * accepted. Otherwise the drag is rejected. + * + */ + public void dragOver(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(FileNode.FILENODE)) + { + Point ploc = e.getLocation(); + TreePath ePath = getPathForLocation(ploc.x,ploc.y); + if (ePath == null) + { + e.rejectDrag(); + return; + } + FileNode node = (FileNode)ePath.getLastPathComponent(); + if(!node.isDirectory()) + e.rejectDrag(); + else + { + setSelectionPath(ePath); + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + } + } + else + e.rejectDrag(); + + return; + } + + public void dropActionChanged(DropTargetDragEvent e) {} + public void dragExit(DropTargetEvent e){} + + +//////////////////// +// AUTO SCROLLING // +//////////////////// + /** + * + * Handles the auto scrolling of the JTree. + * @param location The location of the mouse. + * + */ + public void autoscroll( Point location ) + { + int top = 0, left = 0, bottom = 0, right = 0; + Dimension size = getSize(); + Rectangle rect = getVisibleRect(); + int bottomEdge = rect.y + rect.height; + int rightEdge = rect.x + rect.width; + if( location.y - rect.y < AUTOSCROLL_MARGIN && rect.y > 0 ) + top = AUTOSCROLL_MARGIN; + if( location.x - rect.x < AUTOSCROLL_MARGIN && rect.x > 0 ) + left = AUTOSCROLL_MARGIN; + if( bottomEdge - location.y < AUTOSCROLL_MARGIN && bottomEdge < size.height ) + bottom = AUTOSCROLL_MARGIN; + if( rightEdge - location.x < AUTOSCROLL_MARGIN && rightEdge < size.width ) + right = AUTOSCROLL_MARGIN; + rect.x += right - left; + rect.y += bottom - top; + scrollRectToVisible( rect ); + } + + + /** + * + * Gets the insets used for the autoscroll. + * @return The insets. + * + */ + public Insets getAutoscrollInsets() + { + Dimension size = getSize(); + Rectangle rect = getVisibleRect(); + autoscrollInsets.top = rect.y + AUTOSCROLL_MARGIN; + autoscrollInsets.left = rect.x + AUTOSCROLL_MARGIN; + autoscrollInsets.bottom = size.height - (rect.y+rect.height) + AUTOSCROLL_MARGIN; + autoscrollInsets.right = size.width - (rect.x+rect.width) + AUTOSCROLL_MARGIN; + return autoscrollInsets; + } + + /** + * + * Popup menu listener + * + */ + class PopupListener extends MouseAdapter + { + public void mousePressed(MouseEvent e) + { + maybeShowPopup(e); + } + + public void mouseReleased(MouseEvent e) + { + maybeShowPopup(e); + } + + private void maybeShowPopup(MouseEvent e) + { + if(e.isPopupTrigger()) + popup.show(e.getComponent(), + e.getX(), e.getY()); + } + } + + public static void main(String[] args) + { + JFrame tree_frame = new JFrame("File Manager"); + FileTree ftree = new FileTree(new File(System.getProperty("user.home")), + tree_frame,null); + JScrollPane jsp = new JScrollPane(ftree); + tree_frame.getContentPane().add(jsp); + tree_frame.pack(); + tree_frame.setVisible(true); + } + +} + diff --git a/uk/ac/sanger/artemis/components/FileViewer.java b/uk/ac/sanger/artemis/components/FileViewer.java new file mode 100644 index 000000000..f40fd4e84 --- /dev/null +++ b/uk/ac/sanger/artemis/components/FileViewer.java @@ -0,0 +1,293 @@ +/* FileViewer.java + * + * created: Thu Nov 19 1998 + * + * This file is part of Artemis + * + * Copyright(C) 1998,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/FileViewer.java,v 1.1 2004-06-09 09:46:55 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.awt.*; +import java.awt.event.*; +import java.io.Reader; +import java.io.BufferedReader; +import java.io.IOException; + +import javax.swing.*; + +/** + * This class implements a simple file viewer. In fact any Reader object can + * be viewed. + * + * @author Kim Rutherford + * @version $Id: FileViewer.java,v 1.1 2004-06-09 09:46:55 tjc Exp $ + * + **/ + +public class FileViewer + extends JFrame +{ + + /** Pressing this button will distroy the JFrame. */ + private JButton close_button; + + /** A JPanel to hold the close button. */ + private JPanel button_panel; + + /** The main component we use for displaying the file. */ + private JTextArea text_area = null; + + /** + * The size of the last FileViewer JFrame to be resized. When a new + * FileViewer component is created it is given the current value of this + * variable. This is updated when any FileViewer frame is resized. + **/ + private static Dimension saved_size = null; + + /** + * The position of the last FileViewer JFrame to be moved. When a new + * FileViewer component is created it is given the current value of this + * variable. This is updated when any FileViewer frame is moved. + **/ + private static Point saved_position = null; + + /** + * Create a new FileViewer component and make it visible. + * @param label The name to attach to the new JFrame. + **/ + public FileViewer(final String label) + { + this(label, true); + } + + /** + * Create a new FileViewer component. + * @param label The name to attach to the new JFrame. + * @param visible The new FileViewer will be made visible if and only if + * this argument is true. + **/ + public FileViewer(final String label, final boolean visible) + { + super(label); + + getContentPane().setLayout(new BorderLayout()); + + final Font font = Options.getOptions().getFont(); + + setFont(font); + + text_area = new JTextArea(18, 90); + text_area.setEditable(false); + text_area.setFont(font); + text_area.setBackground(Color.white); + + getContentPane().add(new JScrollPane(text_area), "Center"); + + button_panel = new JPanel(); + getContentPane().add(button_panel, "South"); + + close_button = new JButton("Close"); + close_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + dispose(); + } + }); + + button_panel.add(close_button); + + addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent event) + { + dispose(); + } + }); + + addComponentListener(new ComponentAdapter() + { + public void componentResized(ComponentEvent e) + { + saved_size = FileViewer.this.getSize(); + saved_position = FileViewer.this.getLocation(); + } + public void componentMoved(ComponentEvent e) + { + saved_size = FileViewer.this.getSize(); + saved_position = FileViewer.this.getLocation(); + } + }); + + pack(); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + if(saved_position == null) + { + int screen_height = screen.height; + int screen_width = screen.width; + + if(screen_width <= 800 || screen_height <= 700) + setSize(screen_width * 9 / 10, screen_height * 9 / 10); + else + setSize(800, 700); + + setLocation(new Point((screen.width - getSize().width) / 2, + (screen.height - getSize().height) / 2)); + } + else + { + if(saved_position.x < 0 || saved_position.x + 20 > screen.width) + saved_position.x = 20; + + if(saved_position.y < 0 || saved_position.y + 20 > screen.height) + saved_position.y = 20; + + if(saved_size.width < 50) + saved_size.width = 50; + + if(saved_size.height < 50) + saved_size.height = 50; + + setLocation(saved_position); + setSize(saved_size); + } + + setVisible(visible); + } + + /** + * Clear the viewer window. + **/ + public void clear() + { + text_area.setText(""); + } + + /** + * Read from the given Reader and append the text to this FileViewer. + * @param read_stream The stream to read the contents of the viewer from. + **/ + public void appendFile(Reader read_stream) + throws IOException + { + final BufferedReader buffered_reader = new BufferedReader(read_stream); + String line; + + while((line = buffered_reader.readLine()) != null) + { + appendString(line + "\n"); + Thread.yield(); + } + + buffered_reader.close(); + } + + /** + * Clear the viewer window and display the lines from the given Reader. + * @param read_stream The stream to read the contents of the viewer from. + **/ + public void readFile(Reader read_stream) + throws IOException + { + final BufferedReader buffered_reader = new BufferedReader(read_stream); + + String line; + + final StringBuffer line_buffer = new StringBuffer(); + + while((line = buffered_reader.readLine()) != null) + line_buffer.append(line).append('\n'); + + buffered_reader.close(); + + final String new_text = line_buffer.toString(); + text_area.setText(new_text); +// text_area.getCaret().setDot(0); + text_area.setCaretPosition(0); + } + + /** + * Clear the viewer window and display the lines from the given String. + * @param read_string The string to read the contents of the viewer from. + **/ + public void setText(String read_string) + { + if(!read_string.equals(text_area.getText())) + { + text_area.setText(read_string); +// text_area.getCaret().setDot(0); + text_area.setCaretPosition(0); + } + } + + + /** + * Clear the viewer window and display the lines from the given String. + * @param read_string The string to read the contents of the viewer from. + **/ + public void appendString(String read_string) + { + text_area.append(read_string); + text_area.getCaret().setDot(0); + } + + /** + * Return a String containing the text that this component is displaying. + **/ + public String getText() + { + return getTextArea().getText(); + } + + /** + * Destroy this component. + **/ + public void dispose() + { + setVisible(false); + + saved_size = getSize(); + saved_position = getLocation(); + + super.dispose(); + } + + /** + * return the TextArea component from this FileViewer. + **/ + public JTextArea getTextArea() + { + return text_area; + } + + /** + * Return the JPanel containing the close button of this FileViewer. + **/ + protected JPanel getButtonPanel() + { + return button_panel; + } + +} diff --git a/uk/ac/sanger/artemis/components/GotoMenu.java b/uk/ac/sanger/artemis/components/GotoMenu.java new file mode 100644 index 000000000..a753fedf8 --- /dev/null +++ b/uk/ac/sanger/artemis/components/GotoMenu.java @@ -0,0 +1,510 @@ +/* GotoMenu.java + * + * created: Thu Jan 7 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/GotoMenu.java,v 1.1 2004-06-09 09:46:56 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.*; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A JMenu with commands for moving around the entries. + * + * @author Kim Rutherford + * @version $Id: GotoMenu.java,v 1.1 2004-06-09 09:46:56 tjc Exp $ + **/ + +public class GotoMenu extends SelectionMenu { + /** + * Create a new GotoMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param entry_group The EntryGroup object used to create the Navigator + * component. + * @param menu_name The name of the new menu. + **/ + public GotoMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final String menu_name) { + super (frame, menu_name, selection); + + this.goto_event_source = goto_event_source; + + navigator_item = new JMenuItem ("Navigator ..."); + navigator_item.setAccelerator (NAVIGATOR_KEY); + navigator_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + new Navigator (getSelection (), + GotoMenu.this.goto_event_source, + entry_group); + } + }); + + goto_selection_start_item = new JMenuItem ("Start of Selection"); + goto_selection_start_item.setAccelerator (START_OF_SELECTION_KEY); + goto_selection_start_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + makeSelectionStartVisible (); + } + }); + + goto_selection_end_item = new JMenuItem ("End of Selection"); + goto_selection_end_item.setAccelerator (END_OF_SELECTION_KEY); + goto_selection_end_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + makeSelectionEndVisible (); + } + }); + + goto_feature_start_item = new JMenuItem ("Feature Start"); + goto_feature_start_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + gotoFeatureStart (); + } + }); + + goto_feature_end_item = new JMenuItem ("Feature End"); + goto_feature_end_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + gotoFeatureEnd (); + } + }); + + goto_feature_base = new JMenuItem ("Feature Base Position ..."); + goto_feature_base.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + gotoFeaturePosition (false); + } + }); + + goto_feature_aa_position = new JMenuItem ("Feature Amino Acid ..."); + goto_feature_aa_position.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + gotoFeaturePosition (true); + } + }); + + + goto_first_item = new JMenuItem ("Start of Sequence"); + goto_first_item.setAccelerator (START_OF_SEQUENCE_KEY); + goto_first_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + goto_event_source.gotoFirstBase (); + } + }); + + goto_last_item = new JMenuItem ("End of Sequence"); + goto_last_item.setAccelerator (END_OF_SEQUENCE_KEY); + goto_last_item.addActionListener(new ActionListener () { + public void actionPerformed (ActionEvent event) { + goto_event_source.gotoLastBase (); + } + }); + + add (navigator_item); + addSeparator (); + add (goto_selection_start_item); + add (goto_selection_end_item); + add (goto_feature_start_item); + add (goto_feature_end_item); + add (goto_first_item); + add (goto_last_item); + add (goto_feature_base); + add (goto_feature_aa_position); + } + + /** + * The shortcut for the Navigator. + **/ + final static KeyStroke NAVIGATOR_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_G, InputEvent.CTRL_MASK); + + /** + * The shortcut to go to the start of the selection. + **/ + final static KeyStroke START_OF_SELECTION_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_LEFT, InputEvent.CTRL_MASK); + + /** + * The shortcut to go to the end of the selection. + **/ + final static KeyStroke END_OF_SELECTION_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_RIGHT, InputEvent.CTRL_MASK); + + /** + * The shortcut to go to the start of the sequence. + **/ + final static KeyStroke START_OF_SEQUENCE_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_UP, InputEvent.CTRL_MASK); + + /** + * The shortcut to go to the end of the sequence. + **/ + final static KeyStroke END_OF_SEQUENCE_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_DOWN, InputEvent.CTRL_MASK); + + /** + * Create a new GotoMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param entry_group The EntryGroup object used to create the Navigator + * component. + **/ + public GotoMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group) { + this (frame, selection, goto_event_source, entry_group, "Goto"); + } + + /** + * This method sends an GotoEvent to all the GotoEvent listeners that will + * make the first base of the selection visible. + **/ + private void makeSelectionStartVisible () { + final GotoEvent new_event = + new GotoEvent (this, getSelection ().getStartBaseOfSelection ()); + + goto_event_source.sendGotoEvent (new_event); + } + + /** + * This method sends an GotoEvent to all the GotoEvent listeners that will + * make the last base of the selection visible. + **/ + private void makeSelectionEndVisible () { + final GotoEvent new_event = + new GotoEvent (this, getSelection ().getEndBaseOfSelection ()); + + goto_event_source.sendGotoEvent (new_event); + } + + /** + * Goto the start of the (first) selected feature. (FeatureDisplay only.) + **/ + private void gotoFeatureStart () { + setInternalVariables (); + if (selection_feature == null) { + if (selection_segment == null) { + // do nothing + } else { + // go to the start of the parent feature of the first selected segment + final Feature segment_feature = selection_segment.getFeature (); + makeFeatureStartVisible (segment_feature); + } + } else { + makeFeatureStartVisible (selection_feature); + } + } + + /** + * Goto the end of the (first) selected feature. (FeatureDisplay only.) + **/ + private void gotoFeatureEnd () { + setInternalVariables (); + if (selection_feature == null) { + if (selection_segment == null) { + // do nothing + } else { + // go to the end of the parent feature of the first selected segment + final Feature segment_feature = selection_segment.getFeature (); + makeFeatureEndVisible (segment_feature); + } + } else { + makeFeatureEndVisible (selection_feature); + } + } + + /** + * Goto the start of the (first) selected segment. (FeatureDisplay only.) + **/ + private void gotoSegmentStart () { + setInternalVariables (); + if (selection_segment == null) { + if (selection_feature == null) { + // do nothing + } else { + makeFeatureStartVisible (selection_feature); + } + } else { + makeSegmentStartVisible (selection_segment); + } + } + + /** + * Goto the end of the (first) selected segment. (FeatureDisplay only.) + **/ + private void gotoSegmentEnd () { + setInternalVariables (); + if (selection_segment == null) { + if (selection_feature == null) { + // do nothing + } else { + makeFeatureEndVisible (selection_feature); + } + } else { + makeSegmentEndVisible (selection_segment); + } + } + + /** + * This method will ask the user for a base or amino acid position within + * the first selected feature (using a TextRequester component) and will + * then goto that position. + * @param goto_aa_position If true goto an amino acid position, otherwise + * goto a base position + **/ + private void gotoFeaturePosition (final boolean goto_aa_position) { + if (!checkForSelectionFeatures ()) { + return; + } + + final FeatureVector selected_features = getSelection ().getAllFeatures (); + + if (selected_features.size () > 1) { + new MessageDialog (getParentFrame (), "select only one feature"); + return; + } + + final Feature first_feature = selected_features.elementAt (0); + + final TextRequester text_requester = + goto_aa_position ? + new TextRequester ("amino acid position within selected feature:", + 18, "") : + new TextRequester ("base position within selected feature:", + 18, ""); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + final String position_string = event.getRequesterText ().trim (); + + if (position_string.length () == 0) { + return; + } + + try { + final int feature_position = + Integer.valueOf (position_string).intValue (); + + final Marker sequence_marker; + + if (goto_aa_position) { + // base and aa positions are numbered from 1 + final int aa_position = (feature_position - 1) * 3 + 1; + + sequence_marker = + first_feature.getPositionInSequence (aa_position); + } else { + sequence_marker = + first_feature.getPositionInSequence (feature_position); + } + + final MarkerRange range = new MarkerRange (sequence_marker); + + goto_event_source.gotoBase (sequence_marker); + + /*final*/ MarkerRange selection_range; + + if (goto_aa_position) { + // we want to select the whole codon if we are going to a amino + // acid position + try { + final MarkerRange codon_end_marker_range = + new MarkerRange (sequence_marker.moveBy (2)); + selection_range = + range.combineRanges (codon_end_marker_range, false); + } catch (OutOfRangeException e) { + // just select one base + selection_range = range; + } + } else { + selection_range = range; + } + + // select that base (or range) + getSelection ().setMarkerRange (selection_range); + + } catch (NumberFormatException e) { + new MessageDialog (getParentFrame (), + "this is not a number: " + position_string); + } catch (OutOfRangeException e) { + new MessageDialog (getParentFrame (), "the base position is not " + + "within the selection feature: " + + position_string); + } + } + }); + + text_requester.show (); + } + + /** + * This method sends an GotoEvent to all the GotoEvent listeners that will + * make the given base visible. + **/ + private void makeBaseVisible (final Marker base_marker) { + goto_event_source.gotoBase (base_marker); + } + + /** + * Scroll the display so that the start of the given feature is visible + * (it's start will be in the middle of the screen after the call). + * @param feature The feature to make visible. + **/ + private void makeFeatureStartVisible (Feature feature) { + if (feature == null) { + return; + } + + makeBaseVisible (feature.getFirstBaseMarker ()); + } + + /** + * Scroll the display so that the start of the given feature is visible + * (it's start will be in the middle of the screen after the call). + * @param feature The feature to make visible. + **/ + private void makeFeatureEndVisible (Feature feature) { + if (feature == null) { + return; + } + + makeBaseVisible (feature.getLastBaseMarker ()); + +// System.out.println (feature.getLastBaseMarker ().getPosition ()); +// System.out.println (feature.getLastBaseMarker ().getStrand ()); + } + + /** + * Scroll the display so that the given segment is visible (the first base + * in the segment will be the middle of the screen after the call). + * @param segment The segment to make visible. + **/ + private void makeSegmentStartVisible (FeatureSegment segment) { + if (segment == null) { + return; + } + + final Marker first_base = segment.getStart (); + + makeBaseVisible (first_base); + } + + /** + * Scroll the display so that the given segment is visible (the last base + * in the segment will be the middle of the screen after the call). + * @param segment The segment to make visible. + **/ + private void makeSegmentEndVisible (FeatureSegment segment) { + if (segment == null) { + return; + } + + final Marker last_base = segment.getEnd (); + + makeBaseVisible (last_base); + } + + /** + * Initialise selection_feature and selection_segment. + **/ + private void setInternalVariables () { + final FeatureVector selection_features = + getSelection ().getSelectedFeatures (); + final FeatureSegmentVector selection_segments = + getSelection ().getSelectedSegments (); + + if (selection_features.size () > 0) { + selection_feature = selection_features.elementAt (0); + } else { + selection_feature = null; + } + + if (selection_segments.size () > 0) { + selection_segment = selection_segments.elementAt (0); + } else { + selection_segment = null; + } + } + + /** + * Check that the are some Features in the current selection. If there + * there are some features then return true. If there are no features + * selected then popup a message saying so and return false. + **/ + protected boolean checkForSelection () { + final FeatureVector features_to_check = + getSelection ().getAllFeatures (); + + if (features_to_check.size () == 0) { + new MessageDialog (getParentFrame (), "No features selected"); + return false; + } else { + return true; + } + } + + /** + * The GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource goto_event_source; + + /** + * Set by the constructor to the reference of the first feature in + * selection_features or null if selection_features is empty. + **/ + private Feature selection_feature; + + /** + * Set by the constructor to the reference of the first segment in + * selection_segments or null if selection_segments is empty. + **/ + private FeatureSegment selection_segment; + + private JMenuItem navigator_item = null; + private JMenuItem goto_feature_start_item = null; + private JMenuItem goto_feature_end_item = null; + private JMenuItem goto_selection_end_item = null; + private JMenuItem goto_selection_start_item = null; + private JMenuItem goto_first_item = null; + private JMenuItem goto_last_item = null; + private JMenuItem goto_feature_base = null; + private JMenuItem goto_feature_aa_position = null; +} diff --git a/uk/ac/sanger/artemis/components/GraphMenu.java b/uk/ac/sanger/artemis/components/GraphMenu.java new file mode 100644 index 000000000..ae419651e --- /dev/null +++ b/uk/ac/sanger/artemis/components/GraphMenu.java @@ -0,0 +1,403 @@ +/* GraphMenu.java + * + * created: Tue Sep 18 2001 + * + * This file is part of Artemis + * + * Copyright (C) 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/GraphMenu.java,v 1.1 2004-06-09 09:46:57 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.plot.*; +import uk.ac.sanger.artemis.sequence.*; + +import java.awt.*; +import java.awt.event.*; +import java.io.IOException; +import java.io.File; +import java.io.FileReader; +import java.util.Vector; + +import javax.swing.*; + +/** + * This menu controls one particular BasePlotGroup. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: GraphMenu.java,v 1.1 2004-06-09 09:46:57 tjc Exp $ + **/ + +public class GraphMenu extends JMenu { + /** + * Create a new GraphMenu object and all it's menu items. + * @param frame The JFrame that owns this JMenu. + * @param entry_group The EntryGroup containing the sequence to plot. + * @param base_plot_group The BasePlotGroup that this menu controls. + * @param view_menu This ViewMenu is updated when a usage plot is added. + * @param add_menu This AddMenu is updated when a usage plot is added. + * @param menu_name The name of the new menu. + **/ + public GraphMenu (final JFrame frame, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group, + final FeatureDisplay feature_display, + final String menu_name) { + super (menu_name); + this.frame = frame; + this.entry_group = entry_group; + this.base_plot_group = base_plot_group; + this.view_menu = view_menu; + this.add_menu = add_menu; + this.feature_display = feature_display; + + final BaseAlgorithm [] orig_algorithms = + base_plot_group.getPlotAlgorithms (); + + final JMenuItem hide_all_graphs_item = new JMenuItem ("Hide All Graphs"); + hide_all_graphs_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + final BaseAlgorithm [] current_algorithms = + base_plot_group.getPlotAlgorithms (); + + for (int i = 0 ; i < current_algorithms.length ; ++i) { + final BaseAlgorithm this_algorithm = current_algorithms[i]; + + base_plot_group.setVisibleByAlgorithm (this_algorithm, false); + + final JCheckBoxMenuItem this_menu_item = + (JCheckBoxMenuItem) algorithm_menu_items.elementAt (i); + + this_menu_item.setState (false); + } + if (getParent () != null) { + // XXX change to revalidate(). + frame.validate (); + } + } + }); + add (hide_all_graphs_item); + + addSeparator (); + + if (Options.readWritePossible ()) { + final JMenuItem usage_plot_item = new JMenuItem ("Add Usage Plots ..."); + usage_plot_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + addUsagePlot (); + } + }); + add (usage_plot_item); + + final JMenuItem user_plot_item = new JMenuItem ("Add User Plot ..."); + user_plot_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + addUserPlot (); + } + }); + + add (user_plot_item); + + addSeparator (); + } + + for (int i = 0 ; i < orig_algorithms.length ; ++i) { + final BaseAlgorithm this_algorithm = orig_algorithms[i]; + + addAlgorithm (this_algorithm, false); + } + + if (Options.getOptions ().getProperty ("codon_usage_file") != null) { + final String codon_usage_file_name = + Options.getOptions ().getProperty ("codon_usage_file"); + + try { + addUsagePlot (new File (codon_usage_file_name), true, false); + addUsagePlot (new File (codon_usage_file_name), false, false); + + if (getParent () != null) { + // XXX change to revalidate(). + frame.validate (); + } + } catch (IOException e) { + new MessageDialog (frame, "error while reading usage data: " + e); + } + } + } + + /** + * Create a new GraphMenu object and all it's menu items. + * @param frame The JFrame that owns this JMenu. + * @param entry_group The EntryGroup containing the sequence to plot. + * @param base_plot_group The BasePlotGroup that this menu controls. + * @param view_menu This ViewMenu is updated when a usage plot is added. + * @param add_menu This AddMenu is updated when a usage plot is added. + * @param menu_name The name of the new menu. + **/ + public GraphMenu (final JFrame frame, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group, + final FeatureDisplay feature_display) { + this (frame, entry_group, base_plot_group, feature_display, "Graph"); + } + + /** + * Add a menu item for the given algorithm to the Display menu. + * @param is_visible The JCheckBoxMenuItem starts in the true state if and + * only if this is true. + * @return The new JCheckBoxMenuItem + **/ + public JCheckBoxMenuItem addAlgorithm (final BaseAlgorithm algorithm, + final boolean is_visible) { + + final JCheckBoxMenuItem new_item = + new JCheckBoxMenuItem (algorithm.getAlgorithmName ()); + + new_item.setState (is_visible); + + new_item.addItemListener (new ItemListener () { + public void itemStateChanged(ItemEvent event) { + base_plot_group.setVisibleByAlgorithm (algorithm, + new_item.getState ()); + } + }); + + add (new_item); + + algorithm_menu_items.addElement (new_item); + + return new_item; + } + + /** + * Ask the user for a file name, then read the codon usage data from that + * file, then make and add forward and a reverse BasePlot component using + * the data. + **/ + public void addUsagePlot () { + final JFrame frame = Utilities.getComponentFrame (base_plot_group); + + final StickyFileChooser dialog = new StickyFileChooser (); + + dialog.setDialogTitle ("Select a codon usage data file name ..."); + dialog.setDialogType (JFileChooser.OPEN_DIALOG); + + final int status = dialog.showOpenDialog (frame); + + if (status != JFileChooser.APPROVE_OPTION || + dialog.getSelectedFile () == null) { + return; + } + + final File file = + new File (dialog.getCurrentDirectory (), + dialog.getSelectedFile ().getName ()); + + if (file.length () != 0) { + try { + final BasePlot new_forward_plot = + addUsagePlot (file, true, true); + final BasePlot new_reverse_plot = + addUsagePlot (file, false, true); + + final Algorithm forward_algorithm = new_forward_plot.getAlgorithm (); + final Algorithm reverse_algorithm = new_reverse_plot.getAlgorithm (); + + base_plot_group.setVisibleByAlgorithm (forward_algorithm, true); + base_plot_group.setVisibleByAlgorithm (reverse_algorithm, true); + } catch (IOException e) { + new MessageDialog (Utilities.getComponentFrame (base_plot_group), + "error while reading usage data: " + e); + } + } + } + + /** + * Read the codon usage data from the given File, then make and add a + * BasePlot component using the data. + * @param use_forward_strand The plot will be a forward plot if and only if + * this is true. + * @param is_visible The plot will start off visible if and only if this is + * true. + * @return The BasePlot that was added. + **/ + private BasePlot addUsagePlot (final File codon_usage_file, + final boolean use_forward_strand, + final boolean is_visible) + throws IOException { + final CodonUsageAlgorithm codon_usage_algorithm; + + if (use_forward_strand) { + final Strand forward_strand = + entry_group.getBases ().getForwardStrand (); + + final CodonUsageWeight usage_weights = + new CodonUsageWeight (codon_usage_file, forward_strand); + + codon_usage_algorithm = + new CodonUsageAlgorithm (forward_strand, usage_weights); + } else { + final Strand backward_strand = + entry_group.getBases ().getReverseStrand (); + + final CodonUsageWeight usage_weights = + new CodonUsageWeight (codon_usage_file, backward_strand); + + codon_usage_algorithm = + new CodonUsageAlgorithm (backward_strand, usage_weights); + } + + addAlgorithm (codon_usage_algorithm, is_visible); + + final BasePlot new_plot = + base_plot_group.addAlgorithm (codon_usage_algorithm); + + base_plot_group.setVisibleByAlgorithm (codon_usage_algorithm, is_visible); + + // XXX hack to force the BasePlot to initialise + final DisplayAdjustmentEvent event = + new DisplayAdjustmentEvent (this, + feature_display.getFirstVisibleForwardBase (), + feature_display.getLastVisibleForwardBase (), + feature_display.getMaxVisibleBases (), + feature_display.getScaleValue (), + feature_display.getScaleFactor (), + feature_display.isRevCompDisplay (), + DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT); + + base_plot_group.displayAdjustmentValueChanged (event); + + return new_plot; + } + + /** + * Add a UserDataAlgorithm to the display. + **/ + private void addUserPlot () { + final JFrame frame = Utilities.getComponentFrame (base_plot_group); + + final StickyFileChooser dialog = new StickyFileChooser (); + + dialog.setDialogTitle ("Select a data file name ..."); + dialog.setDialogType (JFileChooser.OPEN_DIALOG); + + final int status = dialog.showOpenDialog (frame); + + if (status != JFileChooser.APPROVE_OPTION || + dialog.getSelectedFile () == null) { + return; + } + + final File file = + new File (dialog.getCurrentDirectory (), + dialog.getSelectedFile ().getName ()); + + if (file.length () != 0) { + final uk.ac.sanger.artemis.util.Document document = + new uk.ac.sanger.artemis.util.FileDocument (file); + + final Strand forward_strand = + getEntryGroup ().getBases ().getForwardStrand (); + + try { + final UserDataAlgorithm new_algorithm = + new UserDataAlgorithm (forward_strand, document); + + final BasePlot new_base_plot = + base_plot_group.addAlgorithm (new_algorithm); + + base_plot_group.setVisibleByAlgorithm (new_algorithm, true); + + addAlgorithm (new_algorithm, true); + + // XXX hack to force the BasePlot to initialise + final DisplayAdjustmentEvent event = + new DisplayAdjustmentEvent (this, + feature_display.getFirstVisibleForwardBase (), + feature_display.getLastVisibleForwardBase (), + feature_display.getMaxVisibleBases (), + feature_display.getScaleValue (), + feature_display.getScaleFactor (), + feature_display.isRevCompDisplay (), + DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT); + + base_plot_group.displayAdjustmentValueChanged (event); + + if (getParent () != null) { + // XXX change to revalidate(). + frame.validate (); + } + } catch (IOException e) { + new MessageDialog (Utilities.getComponentFrame (base_plot_group), + "error while reading user data: " + e); + } + } + } + + /** + * Return the JFrame that was passed to the constructor. + **/ + public JFrame getParentFrame () { + return frame; + } + + /** + * Return the EntryGroup that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + /** + * The JFrame reference that was passed to the constructor. + **/ + private JFrame frame; + + /** + * The BasePlotGroup that was passed to the constructor. + **/ + private BasePlotGroup base_plot_group; + + /** + * The EntryGroup object that was passed to the constructor. + **/ + private EntryGroup entry_group = null; + + /** + * This ViewMenu is updated when a usage plot is added. + **/ + private ViewMenu view_menu; + + /** + * This AddMenu is updated when a usage plot is added. + **/ + private AddMenu add_menu; + + /** + * The FeatureDisplay that was passed to the constructor. + **/ + private FeatureDisplay feature_display; + + /** + * This list of the CheckboxMenuItems for the graphs is stored so that we + * can turn them all off with "Hide All Graphs". + **/ + private Vector algorithm_menu_items = new Vector (); +} diff --git a/uk/ac/sanger/artemis/components/InputStreamProgressDialog.java b/uk/ac/sanger/artemis/components/InputStreamProgressDialog.java new file mode 100644 index 000000000..87f0ab26b --- /dev/null +++ b/uk/ac/sanger/artemis/components/InputStreamProgressDialog.java @@ -0,0 +1,126 @@ +/* InputStreamProgressDialog.java + * + * created: Thu Aug 8 2002 + * + * This file is part of Artemis + * + * Copyright (C) 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/InputStreamProgressDialog.java,v 1.1 2004-06-09 09:46:58 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.awt.*; +import java.awt.event.*; + +import uk.ac.sanger.artemis.util.InputStreamProgressListener; +import uk.ac.sanger.artemis.util.InputStreamProgressEvent; + +import javax.swing.*; + +/** + * A JDialog the show the byte count of an InputStream (via + * InputStreamProgressEvent objects) + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: InputStreamProgressDialog.java,v 1.1 2004-06-09 09:46:58 tjc Exp $ + **/ + +public class InputStreamProgressDialog extends JDialog { + /** + * Create a blocking InputStreamProgressDialog JFrame. + * @param parent The parent window. + * @param message The message to display in the JDialog and to use as the + * frame title. + **/ + public InputStreamProgressDialog (final JFrame parent, + final String message) { + this (parent, message, message, true); + } + + /** + * Create a new InputStreamProgressDialog JFrame. + * @param parent The parent window. + * @param title The title of the new JDialog. + * @param message The message to display in the JDialog. + * @param modal If true, dialog blocks input to the parent window when + * shown. + **/ + public InputStreamProgressDialog (final JFrame parent_frame, + final String title, + final String message, + final boolean modal) { + super (parent_frame, title, modal); + getContentPane ().add (new JLabel (message), "North"); + + final JLabel bytes_label = new JLabel (" "); + getContentPane ().add (bytes_label, "Center"); + + final JPanel panel = new JPanel (); + + panel.add (ok_button); + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + InputStreamProgressDialog.this.dispose (); + } + }); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + InputStreamProgressDialog.this.dispose (); + } + }); + + stream_progress_listener = new InputStreamProgressListener () { + public void progressMade (final InputStreamProgressEvent event) { + + final int char_count = event.getCharCount (); + if (char_count == -1) { + bytes_label.setText (""); + } else { + setVisible (true); + bytes_label.setText ("Characters read so far: " + char_count); + } + } + }; + + getContentPane ().add (panel, "South"); + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + setVisible (false); + } + + /** + * Return an InputStreamProgressListener to pass to FileProgressDocument. + **/ + public InputStreamProgressListener getInputStreamProgressListener () { + return stream_progress_listener; + } + + /** + * An InputStreamProgressListener used to update the label with the + * current number of chars read. + **/ + private InputStreamProgressListener stream_progress_listener = null; + + final private JButton ok_button = new JButton ("OK"); +} diff --git a/uk/ac/sanger/artemis/components/KeyChoice.java b/uk/ac/sanger/artemis/components/KeyChoice.java new file mode 100644 index 000000000..f3241bd12 --- /dev/null +++ b/uk/ac/sanger/artemis/components/KeyChoice.java @@ -0,0 +1,203 @@ +/* KeyChoice.java + * + * created: Mon Sep 6 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/KeyChoice.java,v 1.1 2004-06-09 09:46:59 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.KeyVector; +import uk.ac.sanger.artemis.io.EntryInformation; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This component is a Choice component that shows the possible feature keys. + * + * @author Kim Rutherford + * @version $Id: KeyChoice.java,v 1.1 2004-06-09 09:46:59 tjc Exp $ + **/ +public class KeyChoice extends JPanel { + /** + * Create a new KeyChoice component with CDS as the default key. + * @param entry_information The object to get the list of possible + * keys from. + **/ + public KeyChoice (final EntryInformation entry_information) { + this (entry_information, Key.CDS); + } + + /** + * Create a new KeyChoice component with the given key as the default. + * @param entry_information The object to get the list of possible + * keys from. + **/ + public KeyChoice (final EntryInformation entry_information, + final Key default_key) { + this.entry_information = entry_information; + this.default_key = default_key; + + final Font font = Options.getOptions ().getFont (); + setFont (font); + + key_chooser = new JComboBox (); + + final int MAX_VISIBLE_ROWS = 30; + + key_chooser.setMaximumRowCount (MAX_VISIBLE_ROWS); + + updateChoice (); + } + + /** + * Return the currently selected key. + **/ + public Key getSelectedItem () { + final Key key; + + key = new Key ((String) key_chooser.getSelectedItem ()); + + return key; + } + + /** + * Set the selected Key. + **/ + public void setKey (final Key new_key) { + final int key_index = keyIndex (new_key); + + if (key_index == -1) { + // add the key + key_chooser.addItem (new_key.toString ()); + key_chooser.setSelectedItem (new_key.toString ()); + } else { + key_chooser.setSelectedIndex (key_index); + } + } + + /** + * Adds the specified item listener to receive item events from the Choice + * component of this KeyChoice. + * @param l The item listener. + **/ + public void addItemListener(ItemListener l) { + key_chooser.addItemListener (l); + } + + + /** + * Removes the specified item listener so that it no longer receives item + * events from the Choice component of this KeyChoice. + * @param l The item listener. + **/ + public void removeItemListener(ItemListener l) { + key_chooser.removeItemListener (l); + } + + /** + * Update the key_chooser. + **/ + private void updateChoice () { + removeAll (); + + final Font font = Options.getOptions ().getFont (); + key_chooser.setFont (font); + + add (key_chooser); + + KeyVector keys = getSortedKeys (); + + if (keys == null) { + keys = new KeyVector (); + keys.add (Key.CDS); + } + + for (int i = 0 ; i < keys.size () ; ++i) { + key_chooser.addItem (keys.elementAt (i).toString ()); + } + + if (keyIndex (default_key) != -1) { + setKey (default_key); + } + + // XXX change to revalidate(). + validate (); + + if (getParent () != null) { + // XXX change to revalidate(). + getParent ().validate (); + if (getParent ().getParent () != null) { + // XXX change to revalidate(). + getParent ().getParent ().validate (); + } + } + } + + /** + * Return a alphanumerically sorted vector containing the String + * representations of the common keys (those listed in the common_keys + * property of the options file). If there is no common_keys option then + * all the legal keys are returned. + **/ + private KeyVector getSortedKeys () { + return entry_information.getSortedValidKeys (); + } + + /** + * Return the index in the key_chooser component of the given Key. + **/ + private int keyIndex (final Key key) { + for (int i = 0 ; i < key_chooser.getItemCount () ; ++i) { + if (key.toString ().equals ((String)key_chooser.getItemAt (i))) { + return i; + } + } + return -1; + } + + /** + * The Key that was passed to the constructor. + **/ + private Key default_key = null; + + /** + * The JComboBox component that will show the feature keys. + **/ + private JComboBox key_chooser = null; + + /** + * This toggle sets whether to show common keys or uncommon keys. + **/ + private JCheckBox common_keys_checkbox = null; + + /** + * The EntryInformation object that was passed to the constructor. + **/ + private EntryInformation entry_information = null; +} diff --git a/uk/ac/sanger/artemis/components/KeyChooser.java b/uk/ac/sanger/artemis/components/KeyChooser.java new file mode 100644 index 000000000..af9d93849 --- /dev/null +++ b/uk/ac/sanger/artemis/components/KeyChooser.java @@ -0,0 +1,118 @@ +/* KeyChooser.java + * + * created: Mon Sep 6 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/KeyChooser.java,v 1.1 2004-06-09 09:47:00 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.EntryInformation; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This component is a KeyChoice component in a JFrame (see addItemListener () + * and removeItemListener () for details). + * + * @author Kim Rutherford + * @version $Id: KeyChooser.java,v 1.1 2004-06-09 09:47:00 tjc Exp $ + **/ +public class KeyChooser extends JFrame { + /** + * Create a new KeyChooser component with CDS as the default key. + * @param entry_information The object to get the list of possible + * keys from. + **/ + public KeyChooser (final EntryInformation entry_information) { + this (entry_information, Key.CDS); + } + + /** + * Create a new KeyChooser component with the given key as the default. + * @param entry_information The object to get the list of possible + * keys from. + **/ + public KeyChooser (final EntryInformation entry_information, + final Key default_key) { + key_choice = new KeyChoice (entry_information, default_key); + + getContentPane ().add (key_choice, "Center"); + + final JPanel panel = new JPanel (); + + panel.add (ok_button); + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + KeyChooser.this.dispose (); + } + }); + + panel.add (close_button); + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + KeyChooser.this.dispose (); + } + }); + + getContentPane ().add (panel, "South"); + pack (); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + KeyChooser.this.dispose (); + } + }); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + } + + /** + * Return the KeyChoice component that is displayed in this JFrame. + **/ + public KeyChoice getKeyChoice () { + return key_choice; + } + + /** + * Return the reference of the OK button of this KeyChooser. + **/ + public JButton getOKButton () { + return ok_button; + } + + private KeyChoice key_choice; + + final private JButton ok_button = new JButton ("OK"); + + final private JButton close_button = new JButton ("Cancel"); +} + diff --git a/uk/ac/sanger/artemis/components/ListDialog.java b/uk/ac/sanger/artemis/components/ListDialog.java new file mode 100644 index 000000000..c84d08dc6 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ListDialog.java @@ -0,0 +1,128 @@ +/* ListDialog.java + * + * created: Fri Sep 1 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ListDialog.java,v 1.1 2004-06-09 09:47:01 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This component is a JDialog that contains a List. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ListDialog.java,v 1.1 2004-06-09 09:47:01 tjc Exp $ + **/ + +public class ListDialog extends JDialog { + /** + * Create a new ListDialog component. + **/ + public ListDialog (final JFrame parent, final String title) { + super (parent, title, true); + + JScrollPane scroll_pane = new JScrollPane (getList ()); + + getContentPane ().add (scroll_pane, "Center"); + + getList ().setBackground (Color.white); + + button_panel = new JPanel (); + + button_panel.add (ok_button); + + getContentPane ().add (button_panel, "South"); + + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + selected_item = getList ().getSelectedValue (); + ListDialog.this.dispose (); + } + }); + + button_panel.add (cancel_button); + cancel_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + selected_item = null; + ListDialog.this.dispose (); + } + }); + packme (); + } + + /** + * This method will call pack () and then move the JDialog to the centre of + * the screen. + **/ + private void packme () { + pack (); + + setSize (750, 400); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + final int x_position = (screen.width - getSize ().width) / 2; + int y_position = (screen.height - getSize ().height) / 2; + + if (y_position < 10) { + y_position = 10; + } + + setLocation (new Point (x_position, y_position)); + } + + /** + * Show the dialog and then return the selected item, or null if the user + * hits cancel. + **/ + public Object getSelectedValue () { + show (); + return selected_item; + } + + /** + * Return the reference of the List. + **/ + public JList getList () { + return list; + } + + /** + * The List. + **/ + final private JList list = new JList (); + + private final JButton ok_button = new JButton ("OK"); + private final JButton cancel_button = new JButton ("Cancel"); + + private JPanel button_panel = null; + + /** + * Set to null if and only if the user cancels the dialog, otherwise + * contains the selected item. + **/ + private Object selected_item = null; +} diff --git a/uk/ac/sanger/artemis/components/LogReadListener.java b/uk/ac/sanger/artemis/components/LogReadListener.java new file mode 100644 index 000000000..646fc23a9 --- /dev/null +++ b/uk/ac/sanger/artemis/components/LogReadListener.java @@ -0,0 +1,76 @@ +/* LogReadListener.java + * + * created: Fri Nov 28 2003 + * + * This file is part of Artemis + * + * Copyright (C) 2003 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/LogReadListener.java,v 1.1 2004-06-09 09:47:02 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.io.ReadListener; +import uk.ac.sanger.artemis.io.ReadEvent; + +import javax.swing.*; + +/** + * A class that implements ReadListener by logging all ReadEvents. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: LogReadListener.java,v 1.1 2004-06-09 09:47:02 tjc Exp $ + **/ + +public class LogReadListener implements ReadListener { + /** + * Create a new DialogReadListener. + * @param source The source we are reading from - generally a file name or + * URL. This file name is prepended to the log entries. + **/ + public LogReadListener (final String source) { + this.source = source; + } + + /** + * Implementation of ReadListener. + **/ + public void notify (final ReadEvent event) { + if (source == null) { + Splash.getLogger ().log (event.getMessage () + "\n"); + } else { + Splash.getLogger ().log ("while reading from " + source + ": " + + event.getMessage () + "\n"); + } + seen_message = true; + } + + /** + * Return true if and only if notify() has been called at least once. + **/ + public boolean seenMessage () { + return seen_message; + } + + /** + * The name that was passed to the constructor. + **/ + private String source; + + private boolean seen_message = false; +} diff --git a/uk/ac/sanger/artemis/components/LogViewer.java b/uk/ac/sanger/artemis/components/LogViewer.java new file mode 100644 index 000000000..dbbbf1940 --- /dev/null +++ b/uk/ac/sanger/artemis/components/LogViewer.java @@ -0,0 +1,102 @@ +/* LogViewer.java + * + * created: Wed Aug 30 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/LogViewer.java,v 1.1 2004-06-09 09:47:03 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import java.io.*; + +/** + * A class for viewing log messages in a FileViewer component. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: LogViewer.java,v 1.1 2004-06-09 09:47:03 tjc Exp $ + **/ + +public class LogViewer implements Logger +{ + + /** The FileViewer that is used to show the messages. */ + private FileViewer file_viewer = null; + + /** + * Create a new, empty LogViewer component. + **/ + public LogViewer() + { + + } + + /** + * Send the given String to the log. + **/ + public void log(final String message) + { + maybeMakeViewer(); + file_viewer.appendString(message); + } + + /** + * Read from the given Reader and send it to the log. + **/ + public void log(final Reader reader) + throws IOException + { + maybeMakeViewer(); + file_viewer.appendFile(reader); + } + + /** + * Call setVisible() on the FileViewer that this object contains. + **/ + public void setVisible(final boolean flag) + { + maybeMakeViewer(); + file_viewer.setVisible(flag); + } + + /** + * If file_viewer is null make a FileViewer and assign it to file_viewer. + **/ + private void maybeMakeViewer() + { + if (file_viewer == null) + { + file_viewer = new FileViewer("Log Viewer", false) + { + public void dispose() + { + // if the FileViewer is deleted we want to know + file_viewer = null; + super.dispose(); + } + }; + + file_viewer.pack(); + } + } + +} diff --git a/uk/ac/sanger/artemis/components/MarkerRangeRequester.java b/uk/ac/sanger/artemis/components/MarkerRangeRequester.java new file mode 100644 index 000000000..26b033376 --- /dev/null +++ b/uk/ac/sanger/artemis/components/MarkerRangeRequester.java @@ -0,0 +1,106 @@ +/* MarkerRangeRequester.java + * + * created: Mon Jul 10 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MarkerRangeRequester.java,v 1.1 2004-06-09 09:47:04 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.util.Vector; + +/** + * A requester that gets a MarkerRange or a Range from the user. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: MarkerRangeRequester.java,v 1.1 2004-06-09 09:47:04 tjc Exp $ + **/ + +public class MarkerRangeRequester extends TextRequester { + /** + * Create a new MarkerRangeRequester component with the given prompt. Other + * components can listen for MarkerRangeRequesterEvent object. + * @param prompt A message that is displayed in the component beside the + * TextArea that the user types into. This String is also used as the + * JFrame title. + * @param width The width of the TextRequester in the new requester. + * @param initial_text The initial text to put in the TextRequester. + **/ + public MarkerRangeRequester (final String prompt, + final int width, + final String initial_text) { + super (prompt, width, initial_text); + } + + /** + * Add the given object as a listen for MarkerRangeRequester events from + * this MarkerRangeRequester. + **/ + public void + addMarkerRangeRequesterListener (final MarkerRangeRequesterListener l) { + listeners.addElement (l); + } + + /** + * Send a MarkerRangeRequesterEvent of type OK to all the listeners. + **/ + protected void performOK () { + final MarkerRangeRequesterEvent new_event = + new MarkerRangeRequesterEvent (this, getText (), + MarkerRangeRequesterEvent.OK); + + sendEvent (new_event); + + super.performOK (); + } + + /** + * Send a MarkerRangeRequesterEvent of type CANCEL to all the listeners. + **/ + protected void performCancel () { + final MarkerRangeRequesterEvent new_event = + new MarkerRangeRequesterEvent (this, getText (), + MarkerRangeRequesterEvent.CANCEL); + + sendEvent (new_event); + + super.performCancel (); + } + + /** + * Send the given MarkerRangeRequesterEvent to all the object that are + * listening for the event. + **/ + private void sendEvent (final MarkerRangeRequesterEvent event) { + for (int i = 0 ; i < listeners.size () ; ++i) { + final MarkerRangeRequesterListener listener = + ((MarkerRangeRequesterListener) listeners.elementAt (i)); + + listener.actionPerformed (event); + } + } + + /** + * This contains the objects that are listening for MarkerRangeRequester + * events from this MarkerRangeRequester. + **/ + private Vector listeners = new Vector (); +} diff --git a/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java b/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java new file mode 100644 index 000000000..afa2db47c --- /dev/null +++ b/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java @@ -0,0 +1,199 @@ +/* MarkerRangeRequesterEvent.java + * + * created: Mon Jul 10 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java,v 1.1 2004-06-09 09:47:05 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.io.Range; +import uk.ac.sanger.artemis.io.LocationLexer; +import uk.ac.sanger.artemis.io.LocationLexer.*; +import uk.ac.sanger.artemis.util.*; + +import javax.swing.*; + +/** + * This event is sent when the user presses OK or Cancel in a + * MarkerRangeRequester component. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: MarkerRangeRequesterEvent.java,v 1.1 2004-06-09 09:47:05 tjc Exp $ + **/ + +public class MarkerRangeRequesterEvent extends TextRequesterEvent { + /** + * Create a new MarkerRangeRequesterEvent object. + * @param source The MarkerRangeRequester that generated the event. + * @param requester_text The contents of the TextField in the + * MarkerRangeRequester. + * @param type The type of event. + **/ + public MarkerRangeRequesterEvent (final MarkerRangeRequester source, + final String requester_text, + final int type) { + super (source, requester_text, type); + } + + + /** + * Parse a Range and return the start and end positions in an array that is + * two elements long. + * @return null if and only if there was a parse error. If the range is on + * the forward strand the first element will be less than or equal to the + * second, otherwise the first will be greater than the second. + **/ + private int [] getRangeInternal () { + if (getRequesterText ().length () == 0) { + return null; + } + + final LocationLexer lexer = new LocationLexer (getRequesterText ()); + + final TokenEnumeration enum = lexer.getTokens (); + + boolean complement_flag = false; + + if (enum.peekElement () instanceof String && + ((String)enum.peekElement ()).equals ("complement")) { + complement_flag = true; + enum.nextElement (); + } + + enum.eatToken ('('); + + if (enum.peekElement () instanceof Integer) { + int first_base = ((Integer) enum.nextElement ()).intValue (); + + if (enum.peekElement () instanceof Integer || + (enum.eatToken ("..") || enum.eatToken ('.') || + enum.eatToken ("-")) && + enum.peekElement () instanceof Integer) { + int last_base = ((Integer) enum.nextElement ()).intValue (); + + enum.eatToken (')'); + + if (enum.peekElement () == null) { + if (complement_flag) { + final int temp = first_base; + first_base = last_base; + last_base = temp; + } + + return new int [] { + first_base, last_base + }; + } else { + new MessageDialog ((JFrame) getSource (), + "garbage at the end of the range: " + enum); + return null; + } + } + } + + // if we get to here then there was a parse error + new MessageDialog ((JFrame) getSource (), + "error in range: the range should have this " + + "form: 100..200 - error at: " + enum); + + return null; + } + + /** + * Parse the contents of the String that was passed to the constructor as a + * Range and return it. + * @param bases The Bases object to use to find the Strand that is passed + * to the MarkerRange constructor. + * @return The Range or null if the Range can't be parsed. + **/ + public MarkerRange getMarkerRange (final Bases bases) { + try { + final MarkerRange marker_range; + + final int [] return_values = getRangeInternal (); + + if (return_values == null) { + return null; + } + + final int first_base = return_values[0]; + final int last_base = return_values[1]; + + if (first_base <= last_base) { + // forward strand + final Strand strand; + + strand = bases.getForwardStrand (); + + marker_range = + strand.makeMarkerRangeFromPositions (first_base, + last_base); + } else { + // reverse strand + final Strand strand = bases.getReverseStrand (); + final int raw_first = bases.getComplementPosition (first_base); + final int raw_last = bases.getComplementPosition (last_base); + + marker_range = + strand.makeMarkerRangeFromPositions (raw_last, + raw_first); + } + + return marker_range; + } catch (OutOfRangeException e) { + new MessageDialog ((JFrame) getSource (), + "the bases are out of range for this " + + "sequence"); + return null; + } + } + + /** + * Parse and return a raw Range object from the text. + * @return The Range or null if the range could not be parsed. + **/ + public Range getRawRange () { + final int [] return_values = getRangeInternal (); + + if (return_values == null) { + return null; + } + + final int first_base = return_values[0]; + final int last_base = return_values[1]; + + try { + if (first_base < last_base) { + return new Range (first_base, last_base); + } else { + return new Range (last_base, first_base); + } + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } +} + + diff --git a/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java b/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java new file mode 100644 index 000000000..732421579 --- /dev/null +++ b/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java @@ -0,0 +1,42 @@ +/* MarkerRangeRequesterListener.java + * + * created: Mon Jul 10 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java,v 1.1 2004-06-09 09:47:06 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * This interface is implemented by those classes that need to listen for + * MarkerRangeRequesterEvents. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: MarkerRangeRequesterListener.java,v 1.1 2004-06-09 09:47:06 tjc Exp $ + **/ + +public interface MarkerRangeRequesterListener { + /** + * Invoked when the user presses the OK or Cancel button on a + * MarkerRangeRequester component. + **/ + void actionPerformed (final MarkerRangeRequesterEvent event); +} diff --git a/uk/ac/sanger/artemis/components/MessageDialog.java b/uk/ac/sanger/artemis/components/MessageDialog.java new file mode 100644 index 000000000..18c5a0f52 --- /dev/null +++ b/uk/ac/sanger/artemis/components/MessageDialog.java @@ -0,0 +1,143 @@ +/* MessageDialog.java + * + * created: Mon Dec 14 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MessageDialog.java,v 1.1 2004-06-09 09:47:07 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A popup dialog box that displays a message and has an OK JButton. + * + * @author Kim Rutherford + * @version $Id: MessageDialog.java,v 1.1 2004-06-09 09:47:07 tjc Exp $ + **/ + +public class MessageDialog extends JDialog { + /** + * Create a blocking MessageDialog component. + * @param parent The parent window. + * @param message The message to display in the JDialog and to use as the + * frame title. + **/ + public MessageDialog (final JFrame parent, final String message) { + this (parent, message, message, true); + } + + /** + * Create a new MessageDialog component. + * @param parent The parent window. + * @param message The message to display in the JDialog and to use as the + * frame title. + * @param modal If true, dialog blocks input to the parent window when + * shown. + **/ + public MessageDialog (final JFrame parent, final String message, + final boolean modal) { + this (parent, message, message, modal); + } + + /** + * Create a blocking MessageDialog component. + * @param parent_frame The parent window. + * @param title The title of the new dialog JFrame. + * @param message The message to display in the JDialog. + **/ + public MessageDialog (final JFrame parent_frame, + final String title, + final String message) { + this (parent_frame, title, message, true); + } + + /** + * Create a new MessageDialog component. + * @param parent_frame The parent window. + * @param title The title of the new dialog JFrame. + * @param message The message to display in the JDialog. + * @param modal If true, dialog blocks input to the parent window when + * shown. + **/ + public MessageDialog (final JFrame parent_frame, + final String title, + final String message, + final boolean modal) { + super (parent_frame, title, modal); + + final Font font = Options.getOptions ().getFont (); + setFont (font); + + if (message.length () < MESSAGE_SPLIT_SIZE) { + getContentPane ().add (new JLabel (message), "North"); + } else { + final JTextArea text_area = new JTextArea (18, 90); + text_area.setText (message); + + getContentPane().add (new JScrollPane (text_area), "North"); + text_area.setEditable (false); + } + + final JPanel panel = new JPanel (); + + panel.add (ok_button); + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + MessageDialog.this.dispose (); + } + }); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + MessageDialog.this.dispose (); + } + }); + + addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + MessageDialog.this.dispose (); + } + }); + + getContentPane ().add (panel, "South"); + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + setVisible (true); + } + + /** + * Messages longer than this will be put in a TextArea rather than a Label. + **/ + final private int MESSAGE_SPLIT_SIZE = 100; + + final private JButton ok_button = new JButton ("OK"); +} + diff --git a/uk/ac/sanger/artemis/components/MessageFrame.java b/uk/ac/sanger/artemis/components/MessageFrame.java new file mode 100644 index 000000000..79914703f --- /dev/null +++ b/uk/ac/sanger/artemis/components/MessageFrame.java @@ -0,0 +1,106 @@ +/* MessageFrame.java + * + * created: Mon Jan 18 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MessageFrame.java,v 1.1 2004-06-09 09:47:08 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A popup JFrame box that displays a message and has an OK JButton. + * + * @author Kim Rutherford + * @version $Id: MessageFrame.java,v 1.1 2004-06-09 09:47:08 tjc Exp $ + **/ + +public class MessageFrame extends JFrame { + /** + * Create a new MessageFrame component. + * @param message The message to display in the JDialog and it's title. + **/ + public MessageFrame (final String message) { + this (message, message); + + this.message = new JLabel (message); + } + + /** + * Create a new MessageFrame component. + * @param title The title of the new dialog JFrame. + * @param message The message to display in the JDialog. + **/ + public MessageFrame (final String title, + final String message) { + super (title); + + this.message = new JLabel (message); + + getContentPane ().add (this.message, "North"); + + final JPanel panel = new JPanel (); + + panel.add (ok_button); + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + MessageFrame.this.dispose (); + } + }); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + MessageFrame.this.dispose (); + } + }); + + addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + MessageFrame.this.dispose (); + } + }); + + getContentPane ().add (panel, "South"); + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + setVisible (true); + } + + final private JButton ok_button = new JButton ("OK"); + + /** + * This is the message displayed above the OK button. It can be set with + * setMessage (). + **/ + private JLabel message; +} + + diff --git a/uk/ac/sanger/artemis/components/MultiComparator.java b/uk/ac/sanger/artemis/components/MultiComparator.java new file mode 100644 index 000000000..daeade376 --- /dev/null +++ b/uk/ac/sanger/artemis/components/MultiComparator.java @@ -0,0 +1,1091 @@ +/* MultiComparator.java + * + * created: Tue Sep 11 2001 + * + * This file is part of Artemis + * + * Copyright(C) 2001,2002 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/MultiComparator.java,v 1.1 2004-06-09 09:47:09 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.util.FileDocument; +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.util.InputStreamProgressListener; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.io.DocumentEntryFactory; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.SimpleEntryInformation; + +import java.awt.*; +import java.awt.event.*; +import java.io.IOException; +import java.io.File; +import java.util.Vector; +import java.net.URL; +import javax.swing.*; +import java.awt.dnd.*; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import javax.swing.border.Border; +import javax.swing.border.BevelBorder; + +/** + * This JFrame component contains an arbitrary number of AlignmentViewer + * components and FeatureDisplay components along with ComparatorGlue objects + * to keep them synchronized. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: MultiComparator.java,v 1.1 2004-06-09 09:47:09 tjc Exp $ + **/ + +public class MultiComparator extends JFrame + implements DropTargetListener +{ + + /** The menu_bar is created in makeMenus(). */ + private final JMenuBar menu_bar = new JMenuBar(); + + /** + * An array of EntryGroup objects to display(set by the constructor). The + * array will contain at least one EntryGroup and will be exactly one + * element longer than comparison_data_array. + **/ + private EntryGroup[] entry_group_array = null; + + /** + * An array of ComparisonData objects to display(set by the constructor). + * The array will be exactly one element shorter than entry_group_array. + **/ + private ComparisonData[] comparison_data_array = null; + + /** + * An array of Selection objects - one per EntryGroup. Created by + * makeSelectionArray() + **/ + private Selection[] selection_array = null; + + /** + * An array of GotoEventSource objects - one per EntryGroup. Created by + * makeGotoEventSourceArray() + **/ + private GotoEventSource[] goto_event_source_array = null; + + /** + * An array of FeatureDisplay objects - one per EntryGroup. Created in the + * constructor. + **/ + private FeatureDisplay[] feature_display_array = null; + + /** + * An array of AlignmentViewer objects - one per ComparisonData. Created + * in the constructor. + **/ + private AlignmentViewer[] alignment_viewer_array = null; + + /** + * An array of AlignmentViewer objects - one per ComparisonData. Created + * in the constructor. + **/ + private ComparatorGlue[] comparator_glue_array = null; + + /** + * An array of BasePlotGroup objects - one per FeatureDisplay/EntryGroup. + * Created in the constructor. + **/ + private BasePlotGroup[] base_plot_group_array = null; + + /** This menu is populated by makeFileMenu(). */ + private final JMenu file_menu = new JMenu("File"); + + /** + * The EntrySourceVector reference that is created in the constructor. + **/ + private EntrySourceVector entry_sources; + + /** Used to show the progress of loading file. */ + private InputStreamProgressListener progress_listener; + + /** current group used for drag and drop */ + private int current_group = -1; + + /** + * Initialise entry_group_array and comparison_data_array and create all + * the FeatureDisplay and AlignmentViewer components. + * @param entry_group_array The EntryGroup components to display. + * @param comparison_data_array The ComparisonData to display. This array + * must have one element less than entry_group_array. + * @param progress_listener The object to which InputStreamProgressEvents + * will be send while reading. Currently unused. + **/ + public MultiComparator(final EntryGroup[] entry_group_array, + final ComparisonData[] comparison_data_array, + final InputStreamProgressListener + progress_listener) + { + if(entry_group_array.length != comparison_data_array.length + 1) + throw new Error("internal error - " + + "MultiComparator got illegal arguments"); + + this.entry_group_array = entry_group_array; + this.comparison_data_array = comparison_data_array; + this.progress_listener = progress_listener; + this.entry_sources = Utilities.getEntrySources(this, null); + + setDropTarget(new DropTarget(this,this)); + final StringBuffer title_buffer = new StringBuffer(); + + title_buffer.append("ACT: "); + + for(int i = 0; i < entry_group_array.length - 1; ++i) + { + final String sequence_name = + entry_group_array[i].getDefaultEntry().getName(); + title_buffer.append(sequence_name).append(" vs "); + } + + final EntryGroup last_entry_group = + entry_group_array[entry_group_array.length - 1]; + + title_buffer.append(last_entry_group.getDefaultEntry().getName()); + + setTitle(title_buffer.toString()); + + makeSelectionArray(); + makeGotoEventSourceArray(); + + feature_display_array = + new FeatureDisplay[entry_group_array.length]; + base_plot_group_array = + new BasePlotGroup[entry_group_array.length]; + alignment_viewer_array = + new AlignmentViewer[comparison_data_array.length]; + comparator_glue_array = + new ComparatorGlue[comparison_data_array.length]; + + for(int i = 0; i < getEntryGroupArray().length; ++i) + { + final EntryGroup this_entry_group = getEntryGroupArray()[i]; + + final BasePlotGroup base_plot_group = + new BasePlotGroup(this_entry_group, this, + getSelectionArray()[i], + getGotoEventSourceArray()[i]); + + final int scroll_bar_position; + + if(i == getEntryGroupArray().length - 1 && + getEntryGroupArray().length == 2) + { + // put scrollbar at the top normally, but at the bottom of the lower + // sequence if there's two entries on display + scroll_bar_position = FeatureDisplay.SCROLLBAR_AT_BOTTOM; + } + else + scroll_bar_position = FeatureDisplay.SCROLLBAR_AT_TOP; + + feature_display_array[i] = + new FeatureDisplay(this_entry_group, + getSelectionArray()[i], + getGotoEventSourceArray()[i], + base_plot_group, + scroll_bar_position); + + feature_display_array[i].setShowLabels(false); + feature_display_array[i].setHardLeftEdge(false); + + if(getEntryGroupArray().length > 2) + { + feature_display_array[i].setShowForwardFrameLines(false); + feature_display_array[i].setShowReverseFrameLines(false); + } + + feature_display_array[i].addDisplayAdjustmentListener(base_plot_group); + + base_plot_group_array[i] = base_plot_group; + + this_entry_group.ref(); + } + + for(int i = 0 ; i < getComparisonDataArray().length ; ++i) + { + alignment_viewer_array[i] = + new AlignmentViewer(getFeatureDisplayArray()[i] , + getFeatureDisplayArray()[i + 1], + getComparisonDataArray()[i]); + + comparator_glue_array[i] = + new ComparatorGlue(this, + getFeatureDisplayArray()[i], + getFeatureDisplayArray()[i + 1], + getAlignmentViewerArray()[i]); + } + + final Font default_font = getDefaultFont(); + + setFont(default_font); + + makeMenus(); + + GridBagLayout gridbag = new GridBagLayout(); + getContentPane().setLayout(gridbag); + + GridBagConstraints c = new GridBagConstraints(); + + c.gridwidth = GridBagConstraints.REMAINDER; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.NORTH; + c.gridheight = 1; + c.weightx = 1; + c.weighty = 0; + + for(int i = 0; i < getFeatureDisplayArray().length; ++i) + { + if(!(i == getEntryGroupArray().length - 1 && + getEntryGroupArray().length == 2)) + { + // put graph above the sequence in this case + c.weighty = 0; + gridbag.setConstraints(base_plot_group_array[i], c); + getContentPane().add(base_plot_group_array[i]); + } + + c.weighty = 0; + gridbag.setConstraints(feature_display_array[i], c); + getContentPane().add(feature_display_array[i]); + + if(i == getEntryGroupArray().length - 1 && + getEntryGroupArray().length == 2) + { + // put graph below the sequence in this case + c.weighty = 0; + gridbag.setConstraints(base_plot_group_array[i], c); + getContentPane().add(base_plot_group_array[i]); + } + + if(i < getAlignmentViewerArray().length) + { + c.fill = GridBagConstraints.BOTH; + c.weighty = 1; + gridbag.setConstraints(getAlignmentViewerArray()[i], c); + getContentPane().add(getAlignmentViewerArray()[i]); + } + } + + for(int i = 0 ; i < getEntryGroupArray().length ; ++i) + { + final EntryGroupChangeListener change_listener = + new EntryGroupChangeListener() + { + public void entryGroupChanged(final EntryGroupChangeEvent event) + { + switch(event.getType()) + { + case EntryGroupChangeEvent.ENTRY_ADDED: + case EntryGroupChangeEvent.ENTRY_DELETED: + makeFileMenu(); + break; + } + } + }; + + getEntryGroupArray()[i].addEntryGroupChangeListener(change_listener); + + final EntryChangeListener entry_change_listener = + new EntryChangeListener() + { + public void entryChanged(final EntryChangeEvent event) + { + switch(event.getType()) + { + case EntryChangeEvent.NAME_CHANGED: + makeFileMenu(); + break; + } + } + }; + + getEntryGroupArray()[i].addEntryChangeListener(entry_change_listener); + } + + addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent event) + { + closeComparator(); + } + }); + + final URL icon_url = Splash.class.getResource("/icon.gif"); + + if(icon_url != null) + { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + final Image icon_image = toolkit.getImage(icon_url); + MediaTracker tracker = new MediaTracker(this); + tracker.addImage(icon_image, 0); + + try + { + tracker.waitForAll(); + setIconImage(icon_image); + } + catch(InterruptedException e) + { + // ignore and continue + } + } + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + packme(); + + int screen_height = screen.height; + int screen_width = screen.width; + + if(getAlignmentViewerArray().length > 0) + { + if(screen_width <= 900 || screen_height <= 700) + setSize(screen_width * 9 / 10, screen_height * 9 / 10); + else + setSize(900, 700); + } + + setLocation(new Point((screen.width - getSize().width) / 2, + (screen.height - getSize().height) / 2)); + } + + /** + * Move all the sequences(and comparisons) up one place. The first and + * last EntryGroups should be the same(reference). + **/ + private void rotateSequences() + { + final EntryGroup[] new_entry_groups = + new EntryGroup[getEntryGroupArray().length]; + + final ComparisonData[] new_comparison_data = + new ComparisonData[getComparisonDataArray().length]; + + for(int i = 1 ; i < new_entry_groups.length ; ++i) + new_entry_groups[i - 1] = getEntryGroupArray()[i]; + + new_entry_groups[new_entry_groups.length - 1] = new_entry_groups[0]; + + for(int i = 1; i < new_comparison_data.length; ++i) + new_comparison_data[i - 1] = getComparisonDataArray()[i]; + + new_comparison_data[new_comparison_data.length - 1] = + getComparisonDataArray()[0]; + + final MultiComparator new_comparator = + new MultiComparator(new_entry_groups, new_comparison_data, + progress_listener); + + setVisible(false); + new_comparator.setVisible(true); + closeComparator(); + } + + /** + * If there are no unsaved changes, close this EntryEdit. Otherwise ask + * the user first. + **/ + private void closeComparator() + { + for(int i = 0 ; i < getEntryGroupArray().length ; ++i) + { + final EntryGroup entry_group = getEntryGroupArray()[i]; + + if(entry_group.hasUnsavedChanges() && + entry_group.refCount() == 1) + { + final YesNoDialog yes_no_dialog = + new YesNoDialog(this, + "there are unsaved changes - really close?"); + + if(!yes_no_dialog.getResult()) + return; + } + } + + for(int i = 0 ; i < getEntryGroupArray().length ; ++i) + { + final EntryGroup entry_group = getEntryGroupArray()[i]; + entry_group.unref(); + } + + dispose(); + } + + /** + * This method will call pack() and then move the JFrame to the centre of + * the screen. (Implementation of the FeatureDisplayOwner interface). + **/ + public void packme() + { + pack(); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + final int x_position =(screen.width - getSize().width) / 2; + int y_position =(screen.height - getSize().height) / 2; + + if(y_position < 10) + y_position = 10; + + setLocation(new Point(x_position, y_position)); + } + + /** + * Make one Selection object each EntryGroup. + **/ + private void makeSelectionArray() + { + selection_array = new Selection[getEntryGroupArray().length]; + for(int i = 0 ; i < selection_array.length ; ++i) + selection_array[i] = new Selection(null); + } + + /** + * Make subject_goto_event_source and query_goto_event_source and wire them + * togeather so that goto events do the right thing. + **/ + private void makeGotoEventSourceArray() + { + goto_event_source_array = + new GotoEventSource[getEntryGroupArray().length]; + + for(int i = 0 ; i < goto_event_source_array.length ; ++i) + { + goto_event_source_array[i] = + new SimpleGotoEventSource(getEntryGroupArray()[i]) + { + /** + * Send the given event to all the GotoListeners. + **/ + public void sendGotoEvent(final GotoEvent goto_event) + { + // temporarily disable moving so that the other FeatureDisplay + // doesn't start moving(because of the listeners set up in + // addDisplayListeners() + super.sendGotoEvent(goto_event); + } + }; + } + } + + /** + * Save the given entry, prompting for a file name if necessary. + **/ + private void saveEntry(final Entry entry) + { + if(entry.getName() == null) + { + final EntryFileDialog file_dialog = new EntryFileDialog(this, true); + + file_dialog.saveEntry(entry, true, true, + true, DocumentEntryFactory.ANY_FORMAT); + + } + else + { + try + { + entry.save(DocumentEntryFactory.ANY_FORMAT); + } + catch(IOException e) + { + new MessageDialog(this, "error while saving: " + e); + return; + } + catch(EntryInformationException e) + { + new MessageDialog(this, "error while saving: " + e); + return; + } + } + } + + /** + * Return the name to use for the sub JMenu for the given EntryGroup. + **/ + private String makeNewSubMenuName(final EntryGroup entry_group) + { + final Entry sequence_entry = entry_group.getSequenceEntry(); + + if(sequence_entry == null) + return "(no name)"; + else + { + final String sequence_name = sequence_entry.getName(); + if(sequence_name == null) + return "(no name)"; + else + return sequence_name; + } + + } + + /** + * Make and add the menus for this component. + **/ + private void makeMenus() + { + final Font default_font = getDefaultFont(); + + setJMenuBar(menu_bar); + + makeFileMenu(); + + menu_bar.add(file_menu); + + final JMenu entries_menu = new JMenu("Entries"); + menu_bar.add(entries_menu); + final JMenu select_menu = new JMenu("Select"); + menu_bar.add(select_menu); + final JMenu view_menu = new JMenu("View"); + menu_bar.add(view_menu); + final JMenu goto_menu = new JMenu("Goto"); + menu_bar.add(goto_menu); + final JMenu edit_menu = new JMenu("Edit"); + menu_bar.add(edit_menu); + final JMenu create_menu = new JMenu("Create"); + menu_bar.add(create_menu); + final JMenu write_menu = new JMenu("Write"); + menu_bar.add(write_menu); + JMenu run_menu = null; + if(Options.isUnixHost()) + { + run_menu = new JMenu("Run"); + menu_bar.add(run_menu); + } + final JMenu graph_menu = new JMenu("Graph"); + menu_bar.add(graph_menu); + + for(int i = 0 ; i < getEntryGroupArray().length ; ++i) + { + final EntryGroup entry_group = getEntryGroupArray()[i]; + + final String sub_menu_name = makeNewSubMenuName(entry_group); + + final EntryGroupMenu this_entries_menu = + new EntryGroupMenu(this, + getEntryGroupArray()[i], + sub_menu_name); + entries_menu.add(this_entries_menu); + + final SelectMenu this_select_menu = + new SelectMenu(this, + getSelectionArray()[i], + getGotoEventSourceArray()[i], + getEntryGroupArray()[i], + getBasePlotGroupArray()[i], + sub_menu_name); + select_menu.add(this_select_menu); + + final ViewMenu this_view_menu = + new ViewMenu(this, + getSelectionArray()[i], + getGotoEventSourceArray()[i], + getEntryGroupArray()[i], + getBasePlotGroupArray()[i], + sub_menu_name); + view_menu.add(this_view_menu); + + final GotoMenu this_goto_menu = + new GotoMenu(this, + getSelectionArray()[i], + getGotoEventSourceArray()[i], + getEntryGroupArray()[i], + sub_menu_name); + goto_menu.add(this_goto_menu); + + AddMenu this_create_menu = null; + + if(Options.readWritePossible()) + { + final EditMenu this_edit_menu = + new EditMenu(this, + getSelectionArray()[i], + getGotoEventSourceArray()[i], + getEntryGroupArray()[i], + getBasePlotGroupArray()[i], + sub_menu_name); + edit_menu.add(this_edit_menu); + + this_create_menu = + new AddMenu(this, + getSelectionArray()[i], + getEntryGroupArray()[i], + getGotoEventSourceArray()[i], + getBasePlotGroupArray()[i], + sub_menu_name); + create_menu.add(this_create_menu); + + final WriteMenu this_write_menu = + new WriteMenu(this, + getSelectionArray()[i], + getEntryGroupArray()[i], + sub_menu_name); + write_menu.add(this_write_menu); + + if(Options.isUnixHost()) + { + final RunMenu this_run_menu = + new RunMenu(this, + getSelectionArray()[i], + sub_menu_name); + run_menu.add(this_run_menu); + } + } + + final GraphMenu this_graph_menu = + new GraphMenu(this, + getEntryGroupArray()[i], + getBasePlotGroupArray()[i], + getFeatureDisplayArray()[i], + sub_menu_name); + graph_menu.add(this_graph_menu); + } + + final JMenu display_menu = new JMenu("Display"); + + final JMenuItem hide_on_frame_lines_item = + new JMenuItem("Hide Frame Lines"); + hide_on_frame_lines_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + for(int i = 0 ; i < getFeatureDisplayArray().length ; ++i) + { + getFeatureDisplayArray()[i].setShowForwardFrameLines(false); + getFeatureDisplayArray()[i].setShowReverseFrameLines(false); + } + } + }); + + display_menu.add(hide_on_frame_lines_item); + + final JMenuItem show_on_frame_lines_item = + new JMenuItem("Show Frame Lines"); + show_on_frame_lines_item.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + for(int i = 0 ; i < getFeatureDisplayArray().length ; ++i) + { + getFeatureDisplayArray()[i].setShowForwardFrameLines(true); + getFeatureDisplayArray()[i].setShowReverseFrameLines(true); + } + } + }); + + display_menu.add(show_on_frame_lines_item); + + menu_bar.add(display_menu); + } + + /** + * Make a new File menu replacing the current one(if any). + **/ + private void makeFileMenu() + { + file_menu.removeAll(); + + final EntryGroup[] entry_group_array = getEntryGroupArray(); + + JMenuItem popFileManager = new JMenuItem("Show File Manager ..."); + popFileManager.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + if(ActMain.filemanager == null) + ActMain.filemanager = new FileManager(MultiComparator.this,null); + else + ActMain.filemanager.setVisible(true); + } + }); + file_menu.add(popFileManager); + + if(entry_group_array[0] == + entry_group_array[entry_group_array.length - 1]) + { + final JMenuItem rotate_button = new JMenuItem("Rotate Sequences"); + rotate_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + rotateSequences(); + } + }); + + file_menu.add(rotate_button); + file_menu.addSeparator(); + } + + for(int i = 0; i < getEntryGroupArray().length; ++i) + { + final EntryGroup entry_group = getEntryGroupArray()[i]; + final String new_menu_name = makeNewSubMenuName(entry_group); + final JMenu entry_group_menu = new JMenu(new_menu_name); + file_menu.add(entry_group_menu); + + for(int source_index = 0; source_index < entry_sources.size(); + ++source_index) + { + final EntrySource this_source = + entry_sources.elementAt(source_index); + + if(this_source.isFullEntrySource()) + continue; + + String entry_source_name = this_source.getSourceName(); + String menu_item_name = null; + + if(entry_source_name.equals("Filesystem")) + menu_item_name = "Read An Entry ..."; + else + menu_item_name = "Read An Entry From " + entry_source_name + " ..."; + + final JMenuItem read_entry = new JMenuItem(menu_item_name); + + read_entry.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + readAnEntry(this_source,entry_group); + } + }); + + entry_group_menu.add(read_entry); + } + + entry_group_menu.addSeparator(); + + if(entry_group == null || entry_group.size() == 0) + { + // don't create a menu + } + else + { + final JMenu save_entry_menu = new JMenu("Save Entry"); + + for(int entry_index = 0; entry_index < entry_group.size(); + ++entry_index) + { + final Entry this_entry = entry_group.elementAt(entry_index); + String entry_name = this_entry.getName(); + + if(entry_name == null) + entry_name = "no name"; + + final ActionListener save_entry_listener = + new ActionListener() + { + public void actionPerformed(final ActionEvent event) + { + MultiComparator.this.saveEntry(this_entry); + } + }; + + final JMenuItem save_entry_item = new JMenuItem(entry_name); + save_entry_item.addActionListener(save_entry_listener); + save_entry_menu.add(save_entry_item); + } + + entry_group_menu.add(save_entry_menu); + + final JMenuItem save_all_menu = new JMenuItem("Save All"); + + save_all_menu.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + for(int entry_index = 0; entry_index < entry_group.size(); + ++entry_index) + { + final Entry this_entry = entry_group.elementAt(entry_index); + MultiComparator.this.saveEntry(this_entry); + } + } + }); + + entry_group_menu.add(save_all_menu); + entry_group_menu.addSeparator(); + } + + final JMenuItem edit_subject = new JMenuItem("Edit In Artemis"); + edit_subject.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + final EntryEdit entry_edit = new EntryEdit(entry_group); + entry_edit.setVisible(true); + } + }); + + entry_group_menu.add(edit_subject); + } + + file_menu.addSeparator(); + + final JMenuItem close_button = new JMenuItem("Close"); + close_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + closeComparator(); + } + }); + + file_menu.add(close_button); + } + + + /** + * Read an entry + **/ + private void readAnEntry(final EntrySource this_source, + final EntryGroup entry_group) + { + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + + SwingWorker entryWorker = new SwingWorker() + { + public Object construct() + { + try + { + final Entry new_entry = this_source.getEntry(entry_group.getBases(), + progress_thread, true); + if(new_entry != null) + entry_group.add(new_entry); + } + catch(final OutOfRangeException e) + { + new MessageDialog(MultiComparator.this, + "read failed: one of the features " + + "in the entry has an out of " + + "range location: " + + e.getMessage()); + } + catch(final IOException e) + { + new MessageDialog(MultiComparator.this, + "read failed due to an IO error: " + + e.getMessage()); + } + return null; + } + + public void finished() + { + if(progress_thread !=null) + progress_thread.finished(); + } + }; + entryWorker.start(); + } + + /** + * Read an entry + **/ + private void readAnEntryFromFile(final File file, + final EntryGroup entry_group) + { + final ProgressThread progress_thread = new ProgressThread(null, + "Loading Entry..."); + progress_thread.start(); + + SwingWorker entryWorker = new SwingWorker() + { + public Object construct() + { + try + { + EntryInformation new_entry_information = + new SimpleEntryInformation(Options.getArtemisEntryInformation()); + + final Entry new_entry = new Entry(entry_group.getBases(), + EntryFileDialog.getEntryFromFile(null, + new FileDocument(file), + new_entry_information, false)); + + if(new_entry != null) + entry_group.add(new_entry); + } + catch(final OutOfRangeException e) + { + new MessageDialog(MultiComparator.this, + "read failed: one of the features " + + "in the entry has an out of " + + "range location: " + + e.getMessage()); + } + return null; + } + + public void finished() + { + if(progress_thread !=null) + progress_thread.finished(); + } + }; + entryWorker.start(); + } + + + /** + * Return the current default font(from Diana.options). + **/ + private Font getDefaultFont() + { + return Options.getOptions().getFont(); + } + + /** + * Return the EntryGroup objects that were passed to the constructor. + **/ + private EntryGroup[] getEntryGroupArray() + { + return entry_group_array; + } + + /** + * Return the ComparisonData objects that were passed to the constructor. + **/ + private ComparisonData[] getComparisonDataArray() + { + return comparison_data_array; + } + + /** + * Return the AlignmentViewer objects that were created in the constructor. + **/ + private AlignmentViewer[] getAlignmentViewerArray() + { + return alignment_viewer_array; + } + + /** + * Return the FeatureDisplay objects that were created in the constructor. + **/ + private FeatureDisplay[] getFeatureDisplayArray() + { + return feature_display_array; + } + + /** + * Return the Selection objects that were created in the constructor. + **/ + private Selection[] getSelectionArray() + { + return selection_array; + } + + /** + * Return the GotoEventSource objects that were created in the constructor. + **/ + private GotoEventSource[] getGotoEventSourceArray() + { + return goto_event_source_array; + } + + /** + * Return the BasePlotGroup objects that were created in the constructor. + **/ + private BasePlotGroup[] getBasePlotGroupArray() + { + return base_plot_group_array; + } + + // DropTargetListener methods + + protected static Border dropBorder = new BevelBorder(BevelBorder.LOWERED); + public void drop(DropTargetDropEvent e) + { + Transferable t = e.getTransferable(); + try + { + if(t.isDataFlavorSupported(FileNode.FILENODE)) + { + FileNode fn = (FileNode)t.getTransferData(FileNode.FILENODE); + readAnEntryFromFile(fn.getFile(), + entry_group_array[current_group]); + } + else + e.rejectDrop(); + } + catch(UnsupportedFlavorException ufe) + { + ufe.printStackTrace(); + } + catch(IOException ioe) + { + ioe.printStackTrace(); + } + finally + { + feature_display_array[current_group].setBorder(null); + } + } + + public void dragExit(DropTargetEvent e) + { + for(int i=0;i<feature_display_array.length;i++) + feature_display_array[i].setBorder(null); + } + public void dropActionChanged(DropTargetDragEvent e) {} + + public void dragOver(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(FileNode.FILENODE)) + { + Point ploc = e.getLocation(); + boolean over_feature_display = false; + + for(int i=0;i<feature_display_array.length;i++) + { + int top = feature_display_array[i].getY(); + int btm = top + feature_display_array[i].getHeight(); + + if(ploc.y > top && ploc.y < btm) + { + feature_display_array[i].setBorder(dropBorder); + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + current_group = i; + return; + } + } + + e.rejectDrag(); + } + } + + public void dragEnter(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(FileNode.FILENODE)) + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + } + +} diff --git a/uk/ac/sanger/artemis/components/Navigator.java b/uk/ac/sanger/artemis/components/Navigator.java new file mode 100644 index 000000000..199dd60e7 --- /dev/null +++ b/uk/ac/sanger/artemis/components/Navigator.java @@ -0,0 +1,933 @@ +/* Navigator.java + * + * created: Sun Jan 10 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Navigator.java,v 1.1 2004-06-09 09:47:10 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.util.StringVector; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This component allows the user to navigate around the Entry. + * + * @author Kim Rutherford + * @version $Id: Navigator.java,v 1.1 2004-06-09 09:47:10 tjc Exp $ + **/ + +public class Navigator extends JFrame + implements EntryGroupChangeListener { + /** + * Create a new Navigator component. + * @param selection The Selection that the commands will operate on. + * @param goto_event_source The object the we will call gotoBase () on. + * @param entry_group The EntryGroup object used when searching for + * qualifier text in features. + **/ + public Navigator (final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group) { + super ("Artemis Navigator"); + + this.selection = selection; + this.entry_group = entry_group; + this.goto_event_source = goto_event_source; + + final Font default_font = Options.getOptions ().getFont (); + + GridBagLayout gridbag = new GridBagLayout(); + getContentPane ().setLayout (gridbag); + + setFont (default_font); + + GridBagConstraints c = new GridBagConstraints(); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.NORTH; + c.weighty = 0; + + final int TEXT_FIELD_WIDTH = 25; + + final ButtonGroup button_group = new ButtonGroup (); + + goto_base_button = new JRadioButton ("Goto Base:", true); + + button_group.add (goto_base_button); + + final JPanel goto_base_panel = new JPanel (); + goto_base_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + goto_base_panel.add (goto_base_button); + c.gridwidth = 2; + gridbag.setConstraints (goto_base_panel, c); + getContentPane ().add (goto_base_panel); + + goto_base_text = new JTextField ("", TEXT_FIELD_WIDTH); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (goto_base_text, c); + getContentPane ().add (goto_base_text); + + goto_base_text.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + goto_base_button.setSelected (true); + if (e.getKeyChar () == '\n') { + doGoto (); + } + } + }); + + + goto_gene_name_button = + new JRadioButton ("Goto Feature With Gene Name:", true); + + button_group.add (goto_gene_name_button); + + final JPanel goto_gene_name_panel = new JPanel (); + goto_gene_name_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + goto_gene_name_panel.add (goto_gene_name_button); + c.gridwidth = 2; + gridbag.setConstraints (goto_gene_name_panel, c); + getContentPane ().add (goto_gene_name_panel); + + goto_gene_name_textfield = new JTextField ("", TEXT_FIELD_WIDTH); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (goto_gene_name_textfield, c); + getContentPane ().add (goto_gene_name_textfield); + + goto_gene_name_textfield.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + goto_gene_name_button.setSelected (true); + if (e.getKeyChar () == '\n') { + doGoto (); + } + } + }); + + + goto_qualifier_button = + new JRadioButton ("Goto Feature With This Qualifier Value:", true); + button_group.add (goto_qualifier_button); + + final JPanel goto_qualifier_panel = new JPanel (); + goto_qualifier_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + goto_qualifier_panel.add (goto_qualifier_button); + c.gridwidth = 2; + gridbag.setConstraints (goto_qualifier_panel, c); + getContentPane ().add (goto_qualifier_panel); + + goto_qualifier_textfield = new JTextField ("", TEXT_FIELD_WIDTH); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (goto_qualifier_textfield, c); + getContentPane ().add (goto_qualifier_textfield); + + goto_qualifier_textfield.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + goto_qualifier_button.setSelected (true); + if (e.getKeyChar () == '\n') { + doGoto (); + } + } + }); + + + goto_key_button = + new JRadioButton ("Goto Feature With This Key:", true); + button_group.add (goto_key_button); + + final JPanel goto_key_panel = new JPanel (); + goto_key_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + goto_key_panel.add (goto_key_button); + c.gridwidth = 2; + gridbag.setConstraints (goto_key_panel, c); + getContentPane ().add (goto_key_panel); + + goto_feature_key_textfield = new JTextField ("", TEXT_FIELD_WIDTH); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (goto_feature_key_textfield, c); + getContentPane ().add (goto_feature_key_textfield); + + goto_feature_key_textfield.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + goto_key_button.setSelected (true); + if (e.getKeyChar () == '\n') { + doGoto (); + } + } + }); + + + goto_base_pattern_button = + new JRadioButton ("Find Base Pattern:", true); + button_group.add (goto_base_pattern_button); + + final JPanel goto_base_pattern_panel = new JPanel (); + goto_base_pattern_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + goto_base_pattern_panel.add (goto_base_pattern_button); + c.gridwidth = 2; + gridbag.setConstraints (goto_base_pattern_panel, c); + getContentPane ().add (goto_base_pattern_panel); + + goto_base_pattern_text = new JTextField ("", TEXT_FIELD_WIDTH); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (goto_base_pattern_text, c); + getContentPane ().add (goto_base_pattern_text); + + goto_base_pattern_text.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + goto_base_pattern_button.setSelected (true); + if (e.getKeyChar () == '\n') { + doGoto (); + } + } + }); + + goto_aa_pattern_button = + new JRadioButton ("Find Amino Acid String:", true); + button_group.add (goto_aa_pattern_button); + + final JPanel goto_aa_pattern_panel = new JPanel (); + goto_aa_pattern_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + goto_aa_pattern_panel.add (goto_aa_pattern_button); + c.gridwidth = 2; + gridbag.setConstraints (goto_aa_pattern_panel, c); + getContentPane ().add (goto_aa_pattern_panel); + + goto_aa_pattern_text = new JTextField ("", TEXT_FIELD_WIDTH); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (goto_aa_pattern_text, c); + getContentPane ().add (goto_aa_pattern_text); + + goto_aa_pattern_text.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + goto_aa_pattern_button.setSelected (true); + if (e.getKeyChar () == '\n') { + doGoto (); + } + } + }); + + + goto_base_button.setSelected (true); + + final ButtonGroup start_position_button_group = + new ButtonGroup (); + + final JPanel start_at_an_end_panel = new JPanel (); + + start_at_an_end_button = + new JRadioButton ("Start search at beginning (or end)", true); + + start_position_button_group.add (start_at_an_end_button); + + c.gridwidth = GridBagConstraints.REMAINDER; + + start_at_an_end_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + start_at_an_end_panel.add (start_at_an_end_button); + + gridbag.setConstraints (start_at_an_end_panel, c); + getContentPane ().add (start_at_an_end_panel); + + + final JPanel start_at_selection_panel = new JPanel (); + + start_at_selection_button = + new JRadioButton ("Start search at selection", false); + start_position_button_group.add (start_at_selection_button); + + start_at_selection_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + start_at_selection_panel.add (start_at_selection_button); + + c.gridwidth = GridBagConstraints.REMAINDER; + + gridbag.setConstraints (start_at_selection_panel, c); + getContentPane ().add (start_at_selection_panel); + + + final JPanel option_button_panel = new JPanel (); + + option_button_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + + search_backward_button = new JRadioButton ("Search Backward", false); + ignore_case_button = new JRadioButton ("Ignore Case", true); + partial_match_button = new JRadioButton ("Allow Substring Matches", true); + + option_button_panel.add (search_backward_button); + option_button_panel.add (ignore_case_button); + option_button_panel.add (partial_match_button); + + c.gridwidth = GridBagConstraints.REMAINDER; + + gridbag.setConstraints (option_button_panel, c); + getContentPane ().add (option_button_panel); + + + final JButton goto_button = new JButton ("Goto"); + + goto_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + doGoto (); + } + }); + + + final JButton clear_button = new JButton ("Clear"); + + clear_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + clear (); + } + }); + + + final JButton close_button = new JButton ("Close"); + + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + Navigator.this.dispose (); + } + }); + + + final FlowLayout flow_layout = + new FlowLayout (FlowLayout.CENTER, 15, 5); + + final JPanel close_and_goto_panel = new JPanel (flow_layout); + + close_and_goto_panel.add (goto_button); + close_and_goto_panel.add (clear_button); + close_and_goto_panel.add (close_button); + + gridbag.setConstraints (close_and_goto_panel, c); + getContentPane ().add (close_and_goto_panel); + + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + getEntryGroup ().removeEntryGroupChangeListener (Navigator.this); + Navigator.this.dispose (); + } + }); + + getEntryGroup ().addEntryGroupChangeListener (this); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + setVisible (true); + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can get rid of the Navigator when the + * EntryGroup is no longer in use (for example when the EntryEdit is + * closed). + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.DONE_GONE: + getEntryGroup ().removeEntryGroupChangeListener (this); + dispose (); + break; + } + } + + /** + * This method finds the given pattern in the Bases of the given EntryGroup + * and returns a MarkerRange for it. + * @param pattern This is the pattern to search for. + * @param entry_group The EntryGroup to search. + * @param selection The current selection. + * @param start_at_end If true or if there is nothing in the Selection then + * the search will start at the first or last base (depending on value of + * the next parameter), otherwise the search will start at the Selection. + * @param search_backwards If true the search will move from last base to + * first base, otherwise first to last. + * @return The range that matches the pattern or null if there is no match. + **/ + public static MarkerRange findBasePattern (final BasePattern pattern, + final EntryGroup entry_group, + final Selection selection, + final boolean start_at_end, + final boolean search_backwards) { + // if start_at_end is false we want to start the search at the selection + final Marker selection_base = selection.getLowestBaseOfSelection (); + + final Marker start_position; + + if (start_at_end || selection_base == null) { + // null means start the search from the beginning + start_position = null; + } else { + start_position = selection_base; + } + + final MarkerRange match_range = + pattern.findMatch (entry_group.getBases (), + start_position, + entry_group.getSequenceLength (), + search_backwards); + + if (match_range == null) { + return null; + } else { + return match_range; + } + } + + /** + * This method finds the given amino acid seqeunce, sets the selection to + * the matching bases and then sends a GotoEvent to all the GotoEvent + * listeners that will make the first base of the match visible. + * @param sequence This is the pattern to search for. + * @param entry_group The EntryGroup to search. + * @param selection The current selection. + * @param start_at_end If true or if there is nothing in the Selection then + * the search will start at first or last base (depending on value of the + * next parameter), otherwise the search will start at the Selection. + * @param search_backwards If true the search will move from last base to + * first base, otherwise first to last. + * @return The range that matches the pattern or null if there is no match. + **/ + public static MarkerRange findAminoAcidSequence (final AminoAcidSequence + sequence, + final EntryGroup + entry_group, + final Selection selection, + final boolean start_at_end, + final boolean + search_backwards) { + // if start_at_end is false we want to start the search at the selection + final Marker selection_base = selection.getLowestBaseOfSelection (); + + final Marker start_position; + + if (start_at_end || selection_base == null) { + // null means start the search from the beginning + start_position = null; + } else { + start_position = selection_base; + } + + final MarkerRange match_range; + + if (search_backwards) { + match_range = sequence.findMatch (entry_group.getBases (), + start_position, + true); + } else { + match_range = sequence.findMatch (entry_group.getBases (), + start_position, + false); + } + + if (match_range == null) { + return null; + } else { + return match_range; + } + } + + /** + * This method will perform the selected goto function. + **/ + private void doGoto () { + if (goto_base_button.isSelected ()) { + doGotoBase (); + return; + } + + if (goto_base_pattern_button.isSelected ()) { + doGotoBasePattern (); + return; + } + + if (goto_aa_pattern_button.isSelected ()) { + doGotoAAPattern (); + return; + } + + if (goto_gene_name_button.isSelected ()) { + final StringVector qualifiers_to_search = new StringVector (); + qualifiers_to_search.add (Options.getOptions ().getAllGeneNames ()); + + doGotoQualifierValue (qualifiers_to_search, + goto_gene_name_textfield.getText ().trim ()); + return; + } + + if (goto_qualifier_button.isSelected ()) { + doGotoQualifierValue (null, goto_qualifier_textfield.getText ().trim ()); + return; + } + + if (goto_key_button.isSelected ()) { + doGotoKey (); + return; + } + } + + /** + * Clear all the JTextField components. + **/ + private void clear () { + goto_base_text.setText (""); + goto_base_pattern_text.setText (""); + goto_aa_pattern_text.setText (""); + goto_qualifier_textfield.setText (""); + goto_gene_name_textfield.setText (""); + goto_feature_key_textfield.setText (""); + } + + /** + * Show a message dialog saying that the search text field is empty. + **/ + private void searchTextEmptyError () { + new MessageDialog (this, "You have not entered a value to search for"); + } + + /** + * Go to the base that the user typed in to the goto_base_text TextArea. + **/ + private void doGotoBase () { + final String number_string = goto_base_text.getText ().trim (); + + if (number_string.length () == 0) { + new MessageDialog (this, "you have not entered a number to go to"); + return; + } + + try { + final int destination_base = + Integer.valueOf (number_string).intValue (); + + final MarkerRange destination_range = + goto_event_source.gotoBase (destination_base); + + if (destination_range == null) { + new MessageDialog (this, + "the base is out of range for this sequence"); + } else { + // success select that base + getSelection ().setMarkerRange (destination_range); + } + } catch (NumberFormatException e) { + new MessageDialog (this, "Cannot understand this number: " + + goto_base_text.getText ()); + } + } + + /** + * Go to the base pattern that the user typed in to the + * goto_base_pattern_text TextArea. + **/ + private void doGotoBasePattern () { + try { + final String pattern_string = + goto_base_pattern_text.getText ().trim (); + + if (pattern_string.length () == 0) { + new MessageDialog (this, "you have not entered a pattern to go to"); + return; + } + + final BasePattern pattern = new BasePattern (pattern_string); + + final boolean start_at_an_end = start_at_an_end_button.isSelected (); + + start_at_selection_button.setSelected (true); + + final MarkerRange match_range = + findBasePattern (pattern, + getEntryGroup (), + getSelection (), + start_at_an_end, + search_backward_button.isSelected ()); + + if (match_range == null) { + new MessageDialog (this, "reached the end of sequence"); + } else { + getSelection ().setMarkerRange (match_range); + + final Marker first_selected_base = + getSelection ().getLowestBaseOfSelection (); + + goto_event_source.gotoBase (first_selected_base); + } + } catch (BasePatternFormatException e) { + new MessageDialog (this, + "Illegal base pattern: " + + goto_base_pattern_text.getText ()); + } + } + + /** + * Find the amino acid pattern that the user typed in to the + * goto_aa_pattern_text TextArea. + **/ + private void doGotoAAPattern () { + final String pattern_string = + goto_aa_pattern_text.getText ().trim (); + + if (pattern_string.length () == 0) { + new MessageDialog (this, "you have not entered a pattern to go to"); + return; + } + + final AminoAcidSequence pattern = new AminoAcidSequence (pattern_string); + + final boolean start_at_an_end = start_at_an_end_button.isSelected (); + + start_at_selection_button.setSelected (true); + + final boolean search_backwards = search_backward_button.isSelected (); + + final MarkerRange match_range = + findAminoAcidSequence (pattern, + getEntryGroup (), + getSelection (), + start_at_an_end, + search_backwards); + + if (match_range == null) { + new MessageDialog (this, "reached the end of sequence"); + } else { + getSelection ().setMarkerRange (match_range); + + goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ()); + } + } + + /** + * Select the next feature that contains the text given by the user in the + * goto_qualifier_textfield TextArea. + **/ + private void doGotoQualifierValue (final StringVector qualifiers_to_search, + final String search_text) { + if (search_text.equals ("")) { + searchTextEmptyError (); + return; + } + + final FeatureVector selected_features = + getSelection ().getAllFeatures (); + + final int index; + + if (selected_features.size () == 0 || + start_at_an_end_button.isSelected ()) { + index = -1; + } else { + index = getEntryGroup ().indexOf (selected_features.elementAt (0)); + } + + final int first_search_feature_index; + + Feature found_feature = null; + + if (search_backward_button.isSelected ()) { + if (index == -1) { + // nothing was selected so start the search at the first feature + first_search_feature_index = + getEntryGroup ().getAllFeaturesCount () - 1; + } else { + first_search_feature_index = index - 1; + } + + for (int i = first_search_feature_index ; i >= 0 ; --i) { + final Feature this_feature = getEntryGroup ().featureAt (i); + + if (this_feature.containsText (search_text, + ignore_case_button.isSelected (), + partial_match_button.isSelected (), + qualifiers_to_search)) { + found_feature = this_feature; + break; + } + } + } else { + if (index == -1) { + // nothing was selected so start the search at the first feature + first_search_feature_index = 0; + } else { + first_search_feature_index = index + 1; + } + + for (int i = first_search_feature_index ; + i < getEntryGroup ().getAllFeaturesCount () ; + ++i) { + final Feature this_feature = getEntryGroup ().featureAt (i); + + if (this_feature.containsText (search_text, + ignore_case_button.isSelected (), + partial_match_button.isSelected (), + qualifiers_to_search)) { + + found_feature = this_feature; + break; + } + } + } + + if (found_feature == null) { + getSelection ().clear (); + start_at_an_end_button.setSelected (true); + + new MessageDialog (this, "text not found"); + } else { + getSelection ().set (found_feature); + goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ()); + start_at_selection_button.setSelected (true); + } + } + + /** + * Select the next feature that has the key given by the user in the + * goto_feature_key_textfield TextArea. + **/ + private void doGotoKey () { + final FeatureVector selected_features = + getSelection ().getAllFeatures (); + + final int index; + + if (selected_features.size () == 0 || + start_at_an_end_button.isSelected ()) { + index = -1; + } else { + index = getEntryGroup ().indexOf (selected_features.elementAt (0)); + } + + final int first_search_feature_index; + + final String search_key_string = + goto_feature_key_textfield.getText ().trim (); + + if (search_key_string.equals ("")) { + searchTextEmptyError (); + return; + } + + Feature found_feature = null; + + if (search_backward_button.isSelected ()) { + if (index == -1) { + // nothing was selected so start the search at the first feature + first_search_feature_index = + getEntryGroup ().getAllFeaturesCount () - 1; + } else { + first_search_feature_index = index - 1; + } + + for (int i = first_search_feature_index ; i >= 0 ; --i) { + final Feature this_feature = getEntryGroup ().featureAt (i); + + if (keyMatches (this_feature, search_key_string)) { + found_feature = this_feature; + break; + } + } + } else { + if (index == -1) { + // nothing was selected so start the search at the first feature + first_search_feature_index = 0; + } else { + first_search_feature_index = index + 1; + } + + for (int i = first_search_feature_index ; + i < getEntryGroup ().getAllFeaturesCount () ; + ++i) { + final Feature this_feature = getEntryGroup ().featureAt (i); + + if (keyMatches (this_feature, search_key_string)) { + found_feature = this_feature; + break; + } + } + } + + if (found_feature == null) { + getSelection ().clear (); + start_at_an_end_button.setSelected (true); + + new MessageDialog (this, "key not found"); + } else { + getSelection ().set (found_feature); + goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ()); + start_at_selection_button.setSelected (true); + } + } + + /** + * Returns true if and only if the given feature has search_key_string as + * it's Key. + **/ + public boolean keyMatches (final Feature test_feature, + final String search_key_string) { + final String feature_key_string; + + if (ignore_case_button.isSelected ()) { + feature_key_string = test_feature.getKey ().toString ().toLowerCase (); + } else { + feature_key_string = test_feature.getKey ().toString (); + } + + if (feature_key_string.equals (search_key_string.toLowerCase ())) { + return true; + } else { + return false; + } + } + + /** + * Return the Selection object that was passed to the constructor. + **/ + private Selection getSelection () { + return selection; + } + + /** + * Return the EntryGroup object that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + /** + * The JRadioButton that selects the goto base function. + **/ + final JRadioButton goto_base_button; + + /** + * The JRadioButton that selects the goto base pattern function. + **/ + final JRadioButton goto_base_pattern_button; + + /** + * The JRadioButton that selects the find amino acid sequence function. + **/ + final JRadioButton goto_aa_pattern_button; + + /** + * The JRadioButton that selects the goto feature qualifier value function. + **/ + final JRadioButton goto_qualifier_button; + + /** + * The JRadioButton that selects the goto gene name function. + **/ + final JRadioButton goto_gene_name_button; + + /** + * The JRadioButton that selects the goto feature key function. + **/ + final JRadioButton goto_key_button; + + /** + * This contains the pattern to search for if the user has selected the + * goto base function. + **/ + final JTextField goto_base_text; + + /** + * This contains the pattern to search for if the user has selected the + * goto base pattern function. + **/ + final JTextField goto_base_pattern_text; + + /** + * This contains the pattern to search for if the user has selected the + * goto amino acid function. + **/ + final JTextField goto_aa_pattern_text; + + /** + * This contains the pattern to search for if the user has selected the + * goto qualifier value function. + **/ + final JTextField goto_qualifier_textfield; + + /** + * This contains the pattern to search for if the user has selected the + * goto gene name function. + **/ + final JTextField goto_gene_name_textfield; + + /** + * This contains the key to search for if the user has selected the + * goto key function. + **/ + final JTextField goto_feature_key_textfield; + + /** + * The user selects this JRadioButton if the search should start at first/last + * base or first/last feature (depending on the search type). + **/ + JRadioButton start_at_an_end_button; + + /** + * The user selects this JRadioButton if the search should start at the + * position of the current selection. + **/ + final JRadioButton start_at_selection_button; + + /** + * If checked the search will go backwards. + **/ + final JRadioButton search_backward_button; + + /** + * If checked the search will ignore the case of the query and subject. + **/ + final JRadioButton ignore_case_button; + + /** + * If checked the search text is allowed to match a substring of a + * qualifier value. + **/ + final JRadioButton partial_match_button; + + /** + * The GotoEventSource object that was passed to the constructor. + **/ + final GotoEventSource goto_event_source; + + /** + * The EntryGroup object that was passed to the constructor. + **/ + final EntryGroup entry_group; + + /** + * This is the Selection that was passed to the constructor. + **/ + final private Selection selection; +} diff --git a/uk/ac/sanger/artemis/components/Plot.java b/uk/ac/sanger/artemis/components/Plot.java new file mode 100644 index 000000000..8ee213ebe --- /dev/null +++ b/uk/ac/sanger/artemis/components/Plot.java @@ -0,0 +1,723 @@ +/* Plot.java + * + * created: Thu Dec 17 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Plot.java,v 1.1 2004-06-09 09:47:11 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.plot.*; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This class implements a simple plot component. + * + * @author Kim Rutherford + * @version $Id: Plot.java,v 1.1 2004-06-09 09:47:11 tjc Exp $ + **/ + +public abstract class Plot extends JPanel { + /** + * Create a new plot component. + * @param algorithm The object that will generate the values we plot in + * this component. + * @param draw_scale If true then a scale line will be drawn at the bottom + * of the graph. + **/ + public Plot (Algorithm algorithm, boolean draw_scale) { + this.algorithm = algorithm; + this.draw_scale = draw_scale; + + final Font font = Options.getOptions ().getFont (); + + setFont (font); + FontMetrics fm = getFontMetrics (font); + font_height = fm.getHeight (); + + setLayout(new BorderLayout ()); + + final int MAX_WINDOW; + + if (getAlgorithm ().getDefaultMaxWindowSize () != null) { + MAX_WINDOW = getAlgorithm ().getDefaultMaxWindowSize ().intValue (); + } else { + MAX_WINDOW = 500; + } + + final int MIN_WINDOW; + + if (getAlgorithm ().getDefaultMinWindowSize () != null) { + MIN_WINDOW = getAlgorithm ().getDefaultMinWindowSize ().intValue (); + } else { + MIN_WINDOW = 5; + } + + final int START_WINDOW; + + if (getAlgorithm ().getDefaultWindowSize () == null) { + START_WINDOW = 10; + } else { + START_WINDOW = getAlgorithm ().getDefaultWindowSize ().intValue (); + } + + window_changer = new JScrollBar (Scrollbar.VERTICAL); + window_changer.setValues (START_WINDOW, SCROLL_NOB_SIZE, + MIN_WINDOW, MAX_WINDOW + SCROLL_NOB_SIZE); + if (MAX_WINDOW >= 50) { + window_changer.setBlockIncrement (MAX_WINDOW/50); + } else { + window_changer.setBlockIncrement (1); + } + + window_changer.addAdjustmentListener (new AdjustmentListener () { + public void adjustmentValueChanged(AdjustmentEvent e) { + recalculate_flag = true; + repaintCanvas (); + } + }); + + addComponentListener (new ComponentAdapter () { + public void componentShown(ComponentEvent e) { + recalculate_flag = true; + repaintCanvas (); + } + }); + + add (window_changer, "East"); + + createCanvas (); + + getCanvas ().setBackground (Color.white); + } + + final int SCROLL_NOB_SIZE = 10; + + /** + * Return the algorithm that was passed to the constructor. + **/ + public Algorithm getAlgorithm () { + return algorithm; + } + + /** + * Return the current value of the window size, as set by the + * window_changer scrollbar. + **/ + public int getWindowSize () { + return window_changer.getValue (); + } + + /** + * Create the canvas object for this BasePlot and add it to the component. + **/ + private void createCanvas () { + canvas = new JComponent () { + /** + * Set the offscreen buffer to null as part of invalidation. + **/ + public void invalidate() { + super.invalidate(); + offscreen = null; + } + + /** + * Override update to *not* erase the background before painting + */ + public void update(Graphics g) { + paint(g); + } + + /** + * Paint the canvas. + */ + public void paint(Graphics g) { + paintCanvas (g); + } + }; + + final MouseListener mouse_listener = + new MouseAdapter () { + /** + * Listen for mouse press events so that we can do a popup menu and a + * crosshair. + **/ + public void mousePressed (MouseEvent event) { + if (event.isPopupTrigger () || event.isMetaDown ()) { + final JComponent parent = (JComponent) event.getSource (); + + final JPopupMenu popup = new JPopupMenu ("Plot Options"); + + final JCheckBoxMenuItem scaling_toggle = + new JCheckBoxMenuItem ("Scaling"); + + scaling_toggle.setState (getAlgorithm ().scalingFlag ()); + scaling_toggle.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + getAlgorithm ().setScalingFlag (scaling_toggle.getState ()); + recalculate_flag = true; + repaintCanvas (); + } + }); + + popup.add (scaling_toggle); + + popup.addSeparator (); + + final JMenuItem max_window_size = + new JMenuItem ("Maximum Window Size:"); + + popup.add (max_window_size); + + final int [] window_sizes = { + 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, + 200000, 500000, 1000000 + }; + + for (int i = 0 ; i < window_sizes.length ; ++i) { + final int size = i; + + JMenuItem window_size_item = new JMenuItem (" " + window_sizes[i]); + + window_size_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + final int new_maximum = window_sizes[size]; + if (new_maximum > window_changer.getMinimum ()) { + window_changer.setMaximum (new_maximum + SCROLL_NOB_SIZE); + recalculate_flag = true; + repaintCanvas (); + } + } + }); + + popup.add (window_size_item); + } + + parent.add (popup); + + popup.show (parent, event.getX (), event.getY ()); + } else { + final int point_x = event.getPoint ().x; + final int point_y = event.getPoint ().y; + + if (point_y > getLabelHeight ()) { + cross_hair_position = point_x; + drag_start_position = point_x; + } else { + cancelCrossHairs (); + } + + if (event.getClickCount () == 2) { + fireDoubleClickEvent (); + } else { + fireClickEvent (); + } + + repaintCanvas (); + } + } + }; + + canvas.addMouseListener (mouse_listener); + + final MouseMotionListener mouse_motion_listener = + new MouseMotionAdapter () { + + public void mouseDragged (MouseEvent event) { + final int point_x = event.getPoint ().x; + final int point_y = event.getPoint ().y; + + if (point_y > getLabelHeight ()) { + cross_hair_position = point_x; + } else { + cancelCrossHairs (); + } + + fireDragEvent (); + + repaintCanvas (); + } + }; + + canvas.addMouseMotionListener (mouse_motion_listener); + + add (canvas, "Center"); + } + + /** + * Call mouseClick () on each of the PlotMouseListener objects in the + * listener list. + **/ + private void fireClickEvent () { + for (int i = 0 ; i < listener_list.size () ; ++i) { + final PlotMouseListener listener = + (PlotMouseListener) listener_list.elementAt (i); + listener.mouseClick (getPointPosition (cross_hair_position)); + } + } + + /** + * Call mouseDragged () on each of the PlotMouseListener objects in the + * listener list. + **/ + private void fireDragEvent () { + for (int i = 0 ; i < listener_list.size () ; ++i) { + final PlotMouseListener listener = + (PlotMouseListener) listener_list.elementAt (i); + listener.mouseDrag (getPointPosition (drag_start_position), + getPointPosition (cross_hair_position)); + } + } + + /** + * Call mouseDoubleClick () on each of the PlotMouseListener objects in the + * listener list. + **/ + private void fireDoubleClickEvent () { + for (int i = 0 ; i < listener_list.size () ; ++i) { + final PlotMouseListener listener = + (PlotMouseListener) listener_list.elementAt (i); + listener.mouseDoubleClick (getPointPosition (cross_hair_position)); + } + } + + /** + * Adds the given listener to receive mouse events from this object. + * @param l the listener. + **/ + public void addPlotMouseListener (final PlotMouseListener listener) { + listener_list.addElement (listener); + } + + /** + * Removes the given listener from the list of those objects that receive + * mouse events from this object. + * @param l the listener. + **/ + public void removePlotMouseListener (final PlotMouseListener listener) { + listener_list.removeElement (listener); + } + + /** + * The main paint function for the canvas. An off screen image used for + * double buffering when drawing the canvas. + * @param g The Graphics object of the canvas. + **/ + private void paintCanvas (final Graphics g) { + if (!isVisible ()) { + return; + } + + final int canvas_width = canvas.getSize ().width; + final int canvas_height = canvas.getSize ().height; + + if (canvas_height <= 0 || canvas_width <= 0) { + // there is no point painting a zero width canvas + return; + } + + if (offscreen == null) { + offscreen = createImage (canvas_width, + canvas_height); + } + + Graphics og = offscreen.getGraphics (); + og.setClip (0, 0, canvas_width, canvas_height); + + og.setColor (new Color (240, 240, 240)); + + og.fillRect (0, 0, canvas.getSize ().width, canvas.getSize ().height); + + // Redraw the graph on the canvas using the algorithm from the + // constructor. + drawMultiValueGraph (og); + + drawLabels (og); + + g.drawImage (offscreen, 0, 0, null); + og.dispose (); + } + + /** + * Return the canvas x position of the last click or -1 if the user hasn't + * clicked anywhere yet. + **/ + protected int getCrossHairPosition () { + if (cross_hair_position >= getCanvas ().getSize ().width) { + return -1; + } else { + return cross_hair_position; + } + } + + /** + * Force this component to stop drawing crosshairs. + **/ + protected void cancelCrossHairs () { + cross_hair_position = -1; + drag_start_position = -1; + } + + // the minimum distance in pixels between the labels + private final static int MINIMUM_LABEL_SPACING = 50; + + /** + * Draw the scale line at the bottom of the graph. + * @param start The base on the left + * @param end The base on the right + **/ + protected void drawScaleLine (final Graphics g, + final int start, final int end) { + + final int canvas_width = canvas.getSize ().width; + final int canvas_height = canvas.getSize ().height; + + final int scale_number_y_pos = canvas_height - 1; + + final float bases_per_pixel = 1.0F; + + // set the spacing so that the labels are at multiples of 10 + final int base_label_spacing = MINIMUM_LABEL_SPACING; + + final int label_spacing = (int)(base_label_spacing / bases_per_pixel); + + final int possible_index_of_first_label = start / base_label_spacing; + + final int index_of_first_label; + + if (possible_index_of_first_label == 0) { + index_of_first_label = 1; + } else { + index_of_first_label = possible_index_of_first_label; + } + + final int index_of_last_label = end / base_label_spacing; + + for (int i = index_of_first_label ; + i <= index_of_last_label ; + ++i) { + final String label_string = + String.valueOf ((int)(i * base_label_spacing)); + + final int scale_number_x_pos = + (int) ((i * base_label_spacing - start) / bases_per_pixel); + + g.drawString (label_string, + scale_number_x_pos + 2, + scale_number_y_pos); + + g.drawLine (scale_number_x_pos, canvas_height - getScaleHeight () / 2, + scale_number_x_pos, canvas_height - getScaleHeight ()); + } + } + + /** + * Plot the given points onto a Graphics object. + * @param min_value The minimum of the plot_values. + * @param max_value The maximum of the plot_values. + * @param step_size The current step size for this algorithm. This is + * never greater than window_size. + * @param window_size The window size used in calculating plot_values. + * @param total_unit_count The maximum number of residues/bases we can + * show. This is used to draw the scale line and to calculate the + * distance (in pixels) between plot points. + * @param start_position The distance from the edge of the canvas (measured + * in residues/bases) to start drawing the plot. + * @param plot_values The values to plot. + **/ + protected void drawPoints (final Graphics g, + final float min_value, final float max_value, + final int step_size, final int window_size, + final int total_unit_count, + final int start_position, + final float [] plot_values) { + final float residues_per_pixel = + (float) total_unit_count / canvas.getSize ().width; + + // this is the height of the graph (slightly smaller than the canvas for + // ease of viewing). + final int graph_height = canvas.getSize ().height - + getLabelHeight () - // leave room for the algorithm name + getScaleHeight () - // leave room for the scale + 2; + + if (graph_height < 5) { + // too small to draw + return; + } + + final int number_of_values = plot_values.length; + + for (int i = 0 ; i < number_of_values - 1 ; ++i) { + final int start_residue = + window_size / 2 + i * step_size + start_position; + final int end_residue = + start_residue + step_size; + + final int start_x = (int) (start_residue / residues_per_pixel); + final int end_x = (int) (end_residue / residues_per_pixel); + + // this is a number between 0.0 and 1.0 + final float scaled_start_value = + (plot_values[i] - min_value) / (max_value - min_value); + final int start_y = + graph_height - (int) (scaled_start_value * graph_height) + + getLabelHeight () + 1; + + final float scaled_end_value = + (plot_values[i+1] - min_value) / (max_value - min_value); + final int end_y = + graph_height - (int) (scaled_end_value * graph_height) + + getLabelHeight () + 1; + + g.drawLine (start_x, start_y, end_x, end_y); + } + } + + /** + * Redraw the graph on the canvas using the algorithm. + * @param g The object to draw into. + **/ + protected abstract void drawMultiValueGraph (final Graphics g); + + /** + * Draw a line representing the average of the algorithm over the feature. + * @param g The object to draw into. + * @param min_value The minimum value of the function for the range we are + * viewing + * @param max_value The maximum value of the function for the range we are + * viewing + **/ + protected void drawGlobalAverage (final Graphics g, + final float min_value, + final float max_value) { + final Float average = getAlgorithm ().getAverage (); + + if (average != null) { + g.setColor (Color.gray); + + // this is the height of the graph (slightly smaller than the canvas for + // ease of viewing). + final int graph_height = + canvas.getSize ().height - getFontHeight (); + + // this is a number between 0.0 and 1.0 + final float scaled_average = + (average.floatValue () - min_value) / (max_value - min_value); + + final int position = + graph_height - + (int) (scaled_average * graph_height) + + getFontHeight () + 1; + + g.drawLine (0, position, + canvas.getSize ().width, position); + + final FontMetrics fm = g.getFontMetrics (); + + final int canvas_width = canvas.getSize ().width; + + final String average_string = + String.valueOf (Math.round (average.floatValue () * 100.0) / 100.0); + + g.drawString (average_string, + canvas_width - fm.stringWidth (average_string) - 1, + position); + } + } + + /** + * Put the algorithm name in the top left corner of the canvas and the + * window size in the bottom left. + * @param g The object to draw into. + **/ + private void drawLabels (final Graphics g) { + g.setColor (Color.black); + g.drawString (getAlgorithm ().getAlgorithmName () + + " Window size: " + + String.valueOf (window_changer.getValue ()), + 2, + font_height); + } + + /** + * The method converts the min_value and max_value to String objects and + * then draws them onto the canvas. The min_value is drawn at the bottom + * right, max_value at the top right. + **/ + protected void drawMinMax (final Graphics g, + final float min_value, final float max_value) { + g.setColor (Color.black); + + final int canvas_width = canvas.getSize ().width; + final int canvas_height = canvas.getSize ().height; + + g.drawLine (0, canvas_height - getScaleHeight (), + canvas_width, canvas_height - getScaleHeight ()); + + g.drawLine (0, getLabelHeight (), + canvas_width, getLabelHeight ()); + + final FontMetrics fm = g.getFontMetrics (); + + final String min_string = + String.valueOf (((int) (min_value * 100)) / 100.0); + + g.drawString (min_string, + canvas_width - fm.stringWidth (min_string) - 1, + canvas_height - 1 - getScaleHeight ()); + + final String max_string = + String.valueOf (((int) (max_value * 100)) / 100.0); + + g.drawString (max_string, + canvas_width - fm.stringWidth (max_string) - 1, + 1 + getFontHeight () * 2); + } + + /** + * Draw a vertical line at the given position. + * @param label The label to use on the crosshair + * @param label_pos The position on the line at which the label should be + * drawn (0 is nearest the top). + **/ + protected void drawCrossHair (final Graphics g, final int x_position, + final String label, final int label_pos) { + if (x_position >= 0) { + g.drawLine (x_position, getLabelHeight (), + x_position, canvas.getSize ().height); + + g.drawString (label, x_position + 2, + getFontHeight () * (2 + label_pos) + 2); + } + } + + /** + * Return the JComponent of this Plot. + **/ + protected JComponent getCanvas () { + return canvas; + } + + /** + * Call repaint () on the canvas object. + **/ + protected void repaintCanvas () { + canvas.repaint (); + } + + /** + * Recalculate the values all the state that is used for drawing the plot + **/ + protected abstract void recalculateValues (); + + /** + * Get the position in the Feature or Sequence of the given x canvas + * position. This is the label used when the user clicks the mouse in on + * the canvas (see drawCrossHair ()). + **/ + protected abstract int getPointPosition (final int canvas_x_position); + + /** + * Return the amount of vertical space (in pixels) to use for the scale. + **/ + private int getScaleHeight () { + if (draw_scale) { + return getFontHeight () + 2; + } else { + return 0; + } + } + + /** + * Return the height in algorithm name and label line (returns the font + * height plus a small amount). + **/ + private int getLabelHeight () { + return getFontHeight () + 2; + } + + /** + * Return the height in pixels of the current font. + **/ + private int getFontHeight () { + return font_height; + } + + /** + * The drawing area for this component. + **/ + private JComponent canvas = null; + + /** + * A scroll bar for changing the window size. + **/ + private JScrollBar window_changer = null; + + /** + * The height of the font used in this component. + **/ + private int font_height; + + /** + * Off screen image used for double buffering when drawing the canvas. + **/ + private Image offscreen; + + /** + * The object that will generate the value we plot in this component. + **/ + private Algorithm algorithm; + + /** + * If true then a scale line will be drawn at the bottom of the graph when + * drawScaleLine () is called. + **/ + private boolean draw_scale; + + /** + * Set to true if drawMultiValueGraph () should call recalculateValues (). + * It is reset to false by recalculateValues (). + **/ + protected boolean recalculate_flag = true; + + /** + * The x position of the last click or -1 if the user hasn't clicked + * anywhere yet or the user clicked outside the graph. + **/ + private int cross_hair_position = -1; + + /** + * The x position of the start of the last mouse drag or -1 if the user + * hasn't clicked anywhere yet or the user clicked outside the graph. + **/ + private int drag_start_position = -1; + + /** + * A vector of those objects listening for PlotMouse events. + **/ + final private java.util.Vector listener_list = new java.util.Vector (); +} diff --git a/uk/ac/sanger/artemis/components/PlotMouseListener.java b/uk/ac/sanger/artemis/components/PlotMouseListener.java new file mode 100644 index 000000000..50abff74b --- /dev/null +++ b/uk/ac/sanger/artemis/components/PlotMouseListener.java @@ -0,0 +1,64 @@ +/* PlotMouseListener.java + * + * created: Wed Sep 13 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/PlotMouseListener.java,v 1.1 2004-06-09 09:47:12 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * Implemented by those objects that need to mouse events from a Plot object. + * The coordinates of the mouse click are translated to base/aa positions for + * ease of use. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: PlotMouseListener.java,v 1.1 2004-06-09 09:47:12 tjc Exp $ + **/ + +public interface PlotMouseListener { + /** + * Called when the user clicks somewhere on the plot canvas. + * @param position the base/amino acid position of the click. This is + * -1 if and only if the click was outside the graph (eg. in the label at + * the top) + **/ + void mouseClick (final int position); + + /** + * Called when the user drags the mouse over the plot. + * @param drag_start_position The base/amnino acid position where the drag + * started or -1 if the drag was started outside the graph. + * @param current_position the base/amino acid position of the click. + * This is -1 if and only if the user has dragged the mouse out of + * the graph (eg. in the label at the top) + **/ + void mouseDrag (final int drag_start_position, + final int current_position); + + /** + * Called when the user double-clicks somewhere on the plot. + * @param position the base/amino acid position of the click. This is + * -1 if and only if the click was outside the graph (eg. in the label at + * the top) + **/ + void mouseDoubleClick (final int position); +} diff --git a/uk/ac/sanger/artemis/components/ProcessWatcher.java b/uk/ac/sanger/artemis/components/ProcessWatcher.java new file mode 100644 index 000000000..be0136492 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ProcessWatcher.java @@ -0,0 +1,194 @@ +/* ProcessWatcher.java + * + * created: Mon Oct 4 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/ProcessWatcher.java,v 1.1 2004-06-09 09:47:13 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Logger; + +import java.io.*; + +/** + * Objects of this class watch a Process object and then display a + * MessageFrame window when the process finishes. + * + * @author Kim Rutherford + * @version $Id: ProcessWatcher.java,v 1.1 2004-06-09 09:47:13 tjc Exp $ + **/ + +public class ProcessWatcher + implements Runnable { + /** + * Create a new ProcessWatcher object for the given Process. When the + * process finishes a MessageFrame will alert the user and all + * ProcessWatcherListeners will be informed. + * @param process The Process to watch. + * @param name The name of the process to watch. + **/ + public ProcessWatcher (final Process process, final String name) { + this (process, name, true); + } + + /** + * Create a new ProcessWatcher object for the given Process. When the + * process finishes a MessageFrame will alert the user if and only if the + * alert_user argument is true and all ProcessWatcherListeners will be + * informed. + * @param process The Process to watch. + * @param name The name of the process to watch. + * @param alert_user The user will be informed when a process ends if and + * only if this is true. + **/ + public ProcessWatcher (final Process process, final String name, + final boolean alert_user) { + this.process = process; + this.name = name; + this.alert_user = alert_user; + } + + /** + * This code will wait for the Process to finish then display the return + * value in a MessageFrame. + **/ + public void run () { + try { + final Reader reader = new InputStreamReader (process.getErrorStream ()); + getLogger ().log (reader); + } catch (IOException e) { + final MessageFrame message_frame = + new MessageFrame ("error while reading output of: " + name); + + message_frame.setVisible (true); + } + + try { + final Reader reader = new InputStreamReader (process.getInputStream ()); + getLogger ().log (reader); + } catch (IOException e) { + final MessageFrame message_frame = + new MessageFrame ("error while reading output of: " + name); + + message_frame.setVisible (true); + } + + while (true) { + try { + final int return_value = process.waitFor (); + + for (int i = 0 ; i < listeners.size () ; ++i) { + final ProcessWatcherEvent event = + new ProcessWatcherEvent (process, return_value); + final ProcessWatcherListener listener = + (ProcessWatcherListener) listeners.elementAt (i); + listener.processFinished (event); + } + + final boolean core_dumped = (return_value & 0x80) != 0; + + getLogger ().log ("\n--------------------------------------" + + "---------------------\n\n"); + + final String log_message; + + if (core_dumped) { + log_message = name + " process dumped core"; + new MessageFrame (log_message + + " - check the log window").setVisible (true); + } else { + final int sig_number = return_value & 0x7f; + + if (sig_number > 0) { + log_message = name + " process received signal: " + sig_number; + new MessageFrame (log_message + + " - check the log window").setVisible (true); + } else { + final int exit_code = return_value >> 8; + + if (exit_code == 0) { + log_message = name + " process completed"; + if (alert_user) { + final MessageFrame message_frame = + new MessageFrame (log_message); + message_frame.setVisible (true); + } + } else { + log_message = + name + " process finished with exit code: " + exit_code; + new MessageFrame (log_message + + " - check the log window").setVisible (true); + } + } + } + + getLogger ().log (log_message + "\n"); + + return; + } catch (InterruptedException e) { + // go around the loop again + } + } + } + + /** + * Return the global Logger object. + **/ + private static Logger getLogger () { + return Splash.getLogger (); + } + + /** + * Add the given object as a ProcessWatcherListener for this ProcessWatcher. + **/ + public void addProcessWatcherListener (final ProcessWatcherListener l) { + listeners.addElement (l); + } + + /** + * Remove the given object as a ProcessWatcherListener for this + * ProcessWatcher. + **/ + public void removeProcessWatcherListener (final ProcessWatcherListener l) { + listeners.removeElement (l); + } + + /** + * The Process reference that was passed to the constructor. + **/ + private Process process; + + /** + * The program name that was passed to the constructor. + **/ + private String name; + + /** + * The alert_user argument that was passed to the constructor. + **/ + private boolean alert_user; + + /** + * The list of objects that are listening for ProcessWatcher events. + **/ + private final java.util.Vector listeners = new java.util.Vector (); +} diff --git a/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java b/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java new file mode 100644 index 000000000..61576c0f3 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java @@ -0,0 +1,70 @@ +/* ProcessWatcherEvent.java + * + * created: Tue Feb 29 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java,v 1.1 2004-06-09 09:47:14 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * This event is sent when the process that is watched by ProcessWatcher + * finishes. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ProcessWatcherEvent.java,v 1.1 2004-06-09 09:47:14 tjc Exp $ + **/ + +public class ProcessWatcherEvent { + /** + * Create a new ProcessWatcherEvent object. + * @param process The Process that has finished. + * @param exit_code The exit code of the process that has finished. + **/ + public ProcessWatcherEvent (final Process process, final int exit_code) { + this.process = process; + this.exit_code = exit_code; + } + + /** + * Return the process that was passed to the constructor. + **/ + public Process getProcess () { + return process; + } + + /** + * Return the exit_code that was passed to the constructor. + **/ + public int getExitCode () { + return exit_code; + } + + /** + * The process that was passed to the constructor. + **/ + final Process process; + + /** + * The exit_code that was passed to the constructor. + **/ + final int exit_code; +} diff --git a/uk/ac/sanger/artemis/components/ProcessWatcherListener.java b/uk/ac/sanger/artemis/components/ProcessWatcherListener.java new file mode 100644 index 000000000..723155a4c --- /dev/null +++ b/uk/ac/sanger/artemis/components/ProcessWatcherListener.java @@ -0,0 +1,42 @@ +/* ProcessWatcherListener.java + * + * created: Tue Feb 29 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ProcessWatcherListener.java,v 1.1 2004-06-09 09:47:15 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * This interface is implemented by those classes that need to be notified + * when a Process finishes. The Process must be watched with a + * ProcessWatcher object. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: ProcessWatcherListener.java,v 1.1 2004-06-09 09:47:15 tjc Exp $ + **/ + +public interface ProcessWatcherListener { + /** + * Invoked by a ProcessWatcher object when a Process finishes. + **/ + void processFinished (final ProcessWatcherEvent event); +} diff --git a/uk/ac/sanger/artemis/components/ProgressThread.java b/uk/ac/sanger/artemis/components/ProgressThread.java new file mode 100644 index 000000000..5a83b08f5 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ProgressThread.java @@ -0,0 +1,64 @@ +/* ArtemisMain.java + * + * created: Tue May 11 2004 + * + * This file is part of Artemis + * + * Copyright (C) 2004 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. + * + */ + +package uk.ac.sanger.artemis.components; + +import javax.swing.*; +import java.awt.Dimension; +import java.awt.Color; + +public class ProgressThread extends Thread +{ + private JFrame frame; + private JFrame progress_frame; + private String msg; + private JProgressBar progressBar = new JProgressBar(); + + public ProgressThread(JFrame frame, String msg) + { + this.frame = frame; + this.msg = msg; + } + + public void run() + { + progress_frame = new JFrame("Loading..."); + Dimension d = progress_frame.getToolkit().getScreenSize(); + progressBar.setIndeterminate(true); + progressBar.setBackground(Color.white); + progress_frame.getContentPane().add(progressBar); + progress_frame.pack(); + progress_frame.setLocation( + ((int)d.getWidth()-progress_frame.getWidth())/2, + ((int)d.getHeight()-progress_frame.getHeight())/2); + progress_frame.setVisible(true); + } + + public void finished() + { + if(progress_frame != null) + progress_frame.setVisible(false); + } + +} diff --git a/uk/ac/sanger/artemis/components/QualifierChoice.java b/uk/ac/sanger/artemis/components/QualifierChoice.java new file mode 100644 index 000000000..4cb6c0aa0 --- /dev/null +++ b/uk/ac/sanger/artemis/components/QualifierChoice.java @@ -0,0 +1,181 @@ +/* QualifierChoice.java + * + * created: Tue Sep 7 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/QualifierChoice.java,v 1.1 2004-06-09 09:47:17 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.InvalidKeyException; +import uk.ac.sanger.artemis.io.EntryInformation; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; +import javax.swing.event.*; + +/** + * This is a Choice component that shows only the qualifier names for a given + * key. + * + * @author Kim Rutherford + * @version $Id: QualifierChoice.java,v 1.1 2004-06-09 09:47:17 tjc Exp $ + **/ + +public class QualifierChoice extends JComboBox { + /** + * Create a new QualifierChoice component for the given Key with the given + * qualifier as the default. + * @param entry_information The object to get the list of possible + * qualifiers from. + * @param default_qualifier The name of the Qualifier that should be shown + * initially. If null the first (alphabetically) is selected. + **/ + public QualifierChoice (final EntryInformation entry_information, + final Key key, final String default_qualifier) { + this.entry_information = entry_information; + this.key = key; + + if (default_qualifier != null && + entry_information.isValidQualifier (key, default_qualifier)) { + this.default_qualifier = default_qualifier; + } else { + this.default_qualifier = null; + } + + final int MAX_VISIBLE_ROWS = 30; + + setMaximumRowCount (MAX_VISIBLE_ROWS); + + setEditable(true); + + update (); + } + + /** + * Change the qualifiers shown in this component to be those of the given + * Key. + **/ + public void setKey (final Key key) { + if (this.key != key) { + this.key = key; + + update (); + } + } + + /** + * Select the given qualifier_name. + **/ + private void setSelectedQualifierByName (final String qualifier_name) { + final int index = indexOf (qualifier_name); + + if (index == -1) { + // add the key + addItem (qualifier_name); + setSelectedItem (qualifier_name); + } else { + setSelectedIndex (index); + } + } + + /** + * Return the index in the Choice component of the given qualifier_name. + **/ + private int indexOf (final String qualifier_name) { + for (int i = 0 ; i < getItemCount () ; ++i) { + if (getItemAt (i).equals (qualifier_name)) { + return i; + } + } + return -1; + } + + /** + * Update the Choice to refect the current Key. + **/ + private void update () { + removeAllItems (); + +// final StringVector common_qualifiers = new StringVector (); +// final StringVector uncommon_qualifiers = new StringVector (); + + StringVector qualifier_names = + entry_information.getValidQualifierNames (key); + + if (qualifier_names == null) { + qualifier_names = new StringVector ("note"); + } + + if (default_qualifier != null && + !qualifier_names.contains (default_qualifier)) { + qualifier_names.add (default_qualifier); + } + + qualifier_names.sort (); + +// final StringVector invisible_qualifiers = +// Options.getOptions ().getInvisibleQualifiers (); + + for (int i = 0 ; i < qualifier_names.size () ; ++i) { + final String qualifier_name = qualifier_names.elementAt (i); +// if (!invisible_qualifiers.contains (qualifier_name)) { + addItem (qualifier_name); +// } + } + + if (default_qualifier == null) { + if (indexOf ("note") != -1) { + setSelectedQualifierByName ("note"); + } else { + if (indexOf ("locus_tag") != -1) { + setSelectedQualifierByName ("locus_tag"); + } else { + setSelectedIndex (0); + } + } + } else { + setSelectedQualifierByName (default_qualifier); + } + } + + /** + * The Key that was passed to the constructor. + **/ + private Key key = null; + + /** + * The qualifier name that was passed to the constructor. + **/ + private String default_qualifier = null; + + /** + * The EntryInformation object that was passed to the constructor. + **/ + private EntryInformation entry_information; +} + diff --git a/uk/ac/sanger/artemis/components/QualifierEditor.java b/uk/ac/sanger/artemis/components/QualifierEditor.java new file mode 100644 index 000000000..2e87f0953 --- /dev/null +++ b/uk/ac/sanger/artemis/components/QualifierEditor.java @@ -0,0 +1,332 @@ +/* QualifierEditor.java + * + * created: Tue Oct 23 2001 + * + * This file is part of Artemis + * + * Copyright (C) 2000,2001,2002 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/QualifierEditor.java,v 1.1 2004-06-09 09:47:18 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.util.ReadOnlyException; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.QualifierInfo; +import uk.ac.sanger.artemis.io.QualifierVector; +import uk.ac.sanger.artemis.io.QualifierParseException; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.EntryInformationException; + +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * This component allows qualifiers to be added to or replaced in several + * features at once. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: QualifierEditor.java,v 1.1 2004-06-09 09:47:18 tjc Exp $ + **/ + +public class QualifierEditor extends JFrame { + /** + * Create a new QualifierEditor for the given features. + **/ + public QualifierEditor (final FeatureVector features, + final EntryGroup entry_group) { + super (getFrameTitle (features)); + + this.features = features; + this.entry_group = entry_group; + + final Font font = Options.getOptions ().getFont (); + + final Feature first_feature = features.elementAt (0); + + final EntryInformation entry_information = + first_feature.getEntry ().getEntryInformation (); + + setFont (font); + + final QualifierChoice qualifier_choice = + new QualifierChoice (entry_information, first_feature.getKey (), null); + + final JPanel outer_qualifier_choice_panel = new JPanel (); + final JPanel qualifier_choice_panel = new JPanel (); + outer_qualifier_choice_panel.setLayout (new BorderLayout ()); + + outer_qualifier_choice_panel.add (qualifier_choice_panel, "West"); + + final JButton qualifier_button = new JButton ("Insert qualifier:"); + + qualifier_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + final String qualifier_name = + (String) qualifier_choice.getSelectedItem (); + final QualifierInfo qualifier_info = + entry_information.getQualifierInfo (qualifier_name); + + if (qualifier_info == null) { + new MessageDialog (QualifierEditor.this, "internal error: no " + + "qualifier info for " + qualifier_name); + } else { + qualifier_text_area.append ("/" + qualifier_name); + + switch (qualifier_info.getType ()) { + case QualifierInfo.QUOTED_TEXT: + qualifier_text_area.append ("=\"\""); + break; + + case QualifierInfo.NO_VALUE: + case QualifierInfo.OPTIONAL_QUOTED_TEXT: + break; + + default: + qualifier_text_area.append ("="); + } + + qualifier_text_area.append ("\n"); + } + } + + }); + + qualifier_choice_panel.add (qualifier_button); + + qualifier_choice_panel.add (qualifier_choice); + + getContentPane ().add (outer_qualifier_choice_panel, "North"); + + qualifier_text_area = new QualifierTextArea (); + + add_button.setFont (getFont ()); + replace_button.setFont (getFont ()); + close_button.setFont (getFont ()); + + add_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + addOrReplace (false); + } + }); + + replace_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + addOrReplace (true); + } + }); + + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + dispose (); + } + }); + + button_panel.setFont (getFont ()); + + button_panel.add (replace_button); + button_panel.add (add_button); + button_panel.add (close_button); + + getContentPane ().add (qualifier_text_area, "Center"); + getContentPane ().add (button_panel, "South"); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + dispose (); + } + }); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + } + + /** + * Add to or replace the qualifiers in all the features that were passed to + * the constructor with the qualifiers from qualifier_text_area. + * @param replace If false any existing qualifiers of the same name in the + * features are left unchanged. If true existing qualifiers of the same + * name in the features will be deleted. + **/ + private void addOrReplace (final boolean replace) { + try { + entry_group.getActionController ().startAction (); + + // this will contain one QualifierVector object for each Feature in the + // features vector (in the same order) + final Vector qualifier_vector_vector = new Vector (); + + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + + final Entry this_feature_entry = this_feature.getEntry (); + + if (this_feature_entry == null) { + // feature has already been deleted + qualifier_vector_vector.addElement (null); + } else { + final EntryInformation entry_information = + this_feature_entry.getEntryInformation (); + + try { + final QualifierVector qualifiers = + qualifier_text_area.getParsedQualifiers (entry_information); + + qualifier_vector_vector.addElement (qualifiers); + + } catch (QualifierParseException e) { + new MessageDialog (this, + "error while parsing: " + e.getMessage ()); + return; + } + } + } + + if (qualifier_vector_vector.size () != features.size ()) { + throw new Error ("Internal error in QualifierEditor.add() - " + + "mismatched array sizes"); + } + + for (int feature_index = 0 ; + feature_index < features.size () ; + ++feature_index) { + final Feature this_feature = features.elementAt (feature_index); + + if (qualifier_vector_vector.elementAt (feature_index) == null) { + continue; + } + + final QualifierVector qualifier_vector = + (QualifierVector) qualifier_vector_vector.elementAt (feature_index); + + for (int qualifier_index = 0 ; + qualifier_index < qualifier_vector.size () ; + ++qualifier_index) { + final Qualifier this_qualifier = + qualifier_vector.elementAt (qualifier_index); + + if (replace) { + try { + this_feature.setQualifier (this_qualifier); + } catch (EntryInformationException e) { + new MessageDialog (this, + "failed to add qualifiers to: " + + this_feature.getIDString () + ": " + + e.getMessage ()); + } catch (ReadOnlyException e) { + new MessageDialog (this, + "failed to add qualifiers to read-only " + + "feature: " + this_feature.getIDString () + + ": " + e.getMessage ()); + } + } else { + try { + this_feature.addQualifierValues (this_qualifier); + } catch (EntryInformationException e) { + new MessageDialog (this, + "failed to add qualifiers to: " + + this_feature.getIDString () + ": " + + e.getMessage ()); + } catch (ReadOnlyException e) { + new MessageDialog (this, + "failed to add qualifiers to read-only " + + "feature: " + this_feature.getIDString () + + ": " + e.getMessage ()); + } + + } + } + } + } finally { + entry_group.getActionController ().endAction (); + } + } + + /** + * Return an appropriate String to use for the title of this JFrame. + **/ + static private String getFrameTitle (final FeatureVector features) { + boolean etc_flag = false; + + final StringBuffer buffer = new StringBuffer (); + + final int MAX_LENGTH = 80; + + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + + final String feature_name = this_feature.getIDString (); + + if (feature_name == null) { + etc_flag = true; + continue; + } + + if (buffer.length () + feature_name.length () < MAX_LENGTH) { + if (buffer.length () == 0) { + buffer.append ("Add or replace qualifiers of: "); + buffer.append (feature_name); + } else { + buffer.append (", ").append (feature_name); + } + } else { + etc_flag = true; + break; + } + } + + if (buffer.length () == 0) { + buffer.append ("Add or replace qualifiers"); + } else { + if (etc_flag) { + buffer.append (", ..."); + } + } + + return buffer.toString (); + } + + private QualifierTextArea qualifier_text_area; + + private JButton add_button = new JButton ("Add"); + private JButton replace_button = new JButton ("Replace"); + private JButton close_button = new JButton ("Close"); + + private FlowLayout flow_layout = + new FlowLayout (FlowLayout.CENTER, 25, 5); + + private JPanel button_panel = new JPanel (flow_layout); + + /** + * The Feature objects that were passed to the constructor. + **/ + private FeatureVector features = new FeatureVector (); + + /** + * The EntryGroup that contains the Features (passed to the constructor). + **/ + private EntryGroup entry_group; +} diff --git a/uk/ac/sanger/artemis/components/QualifierTextArea.java b/uk/ac/sanger/artemis/components/QualifierTextArea.java new file mode 100644 index 000000000..cb9bf1849 --- /dev/null +++ b/uk/ac/sanger/artemis/components/QualifierTextArea.java @@ -0,0 +1,96 @@ +/* QualifierTextArea.java + * + * created: Tue Oct 23 2001 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/QualifierTextArea.java,v 1.1 2004-06-09 09:47:19 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; + +import uk.ac.sanger.artemis.io.QualifierParseException; +import uk.ac.sanger.artemis.io.QualifierVector; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.EmblStreamFeature; + +import java.awt.*; +import java.io.*; +import javax.swing.*; + +/** + * This component is a TextArea that understands qualifiers. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: QualifierTextArea.java,v 1.1 2004-06-09 09:47:19 tjc Exp $ + **/ + +public class QualifierTextArea extends JTextArea { + /** + * Create a new QualifierTextArea containing no text. + **/ + public QualifierTextArea () { + super ((Options.getOptions ().getPropertyTruthValue ("alicat_mode") || + Options.getOptions ().getPropertyTruthValue ("val_mode") ? + 40 : + 18), + 81); + setLineWrap (true); + setBackground (Color.white); + + setDragEnabled(true); + } + + /** + * Parse and return the qualifiers in this TextArea in a QualifierVector. + **/ + public QualifierVector + getParsedQualifiers (final EntryInformation entry_information) + throws QualifierParseException { + final String qualifier_string = getText (); + return getQualifiersFromString (qualifier_string, + entry_information); + } + + /** + * Return a QualifierVector containing the qualifiers from a String. + * @param qual_string contains the qualifiers to parse + */ + private static QualifierVector + getQualifiersFromString (final String qual_string, + final EntryInformation entry_information) + throws QualifierParseException { + + try { + final StringReader string_reader = new StringReader (qual_string); + + final QualifierVector embl_qualifiers = + EmblStreamFeature.readQualifiers (string_reader, + entry_information); + + string_reader.close (); + + return embl_qualifiers; + } catch (IOException exception) { + throw (new QualifierParseException (exception.getMessage ())); + } + } +} diff --git a/uk/ac/sanger/artemis/components/RunMenu.java b/uk/ac/sanger/artemis/components/RunMenu.java new file mode 100644 index 000000000..8be27bef1 --- /dev/null +++ b/uk/ac/sanger/artemis/components/RunMenu.java @@ -0,0 +1,278 @@ +/* RunMenu.java + * + * created: Fri Jan 22 1999 + * + * This file is part of Artemis + * + * Copyright(C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or(at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/RunMenu.java,v 1.1 2004-06-09 09:47:20 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.util.ReadOnlyException; +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.io.EntryInformationException; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.InvalidKeyException; +import uk.ac.sanger.artemis.io.InvalidRelationException; + +import java.io.IOException; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * A JMenu of external commands/functions. + * + * @author Kim Rutherford + * @version $Id: RunMenu.java,v 1.1 2004-06-09 09:47:20 tjc Exp $ + **/ + +public class RunMenu extends SelectionMenu +{ + /** + * Create a new RunMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param menu_name The name of the new menu. + **/ + public RunMenu(final JFrame frame, final Selection selection, + final String menu_name) + { + super(frame, menu_name, selection); + + final ExternalProgramVector external_programs = + Options.getOptions().getExternalPrograms(); + + for(int i = 0 ; i < external_programs.size() ; ++i) + makeMenuItem(external_programs.elementAt(i)); + + addSeparator(); + + for(int i = 0 ; i < external_programs.size() ; ++i) + makeOptionsMenuItem(external_programs.elementAt(i)); + + if(Options.getOptions().getProperty("jcon_min_jobs") != null) + { + addSeparator(); + final JMenuItem jcon_status = new JMenuItem("Show Job Status ..."); + + jcon_status.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + try + { + final int ids[] = getIds(); + TaskViewerFrame tvf = new TaskViewerFrame(ids); + + tvf.setSize(400, 600); + tvf.setVisible(true); + } + catch(Exception e) + { + e.printStackTrace(); + new MessageDialog(frame, "unable to view job status: " + e); + } + } + }); + + add(jcon_status); + } + } + + /** + * Create a new RunMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + **/ + public RunMenu(final JFrame frame, + final Selection selection) + { + this(frame, selection, "Run"); + } + + /** + * Make a new menu item for running the given ExternalProgram object. + * @param program Create two menu items for this program. + **/ + private void makeMenuItem(final ExternalProgram program) + { + final JMenuItem new_menu; + + if(program.getType() == ExternalProgram.AA_PROGRAM || + program.getType() == ExternalProgram.DNA_PROGRAM && + Options.getOptions().getPropertyTruthValue("sanger_options")) + { + final String options_string = program.getProgramOptions(); + + if(options_string.length() > 0) + new_menu = new JMenuItem("Run " + program.getName() + " (" + + options_string + ") on selected features"); + else + new_menu = + new JMenuItem("Run " + program.getName() + " on selected features"); + } + else + new_menu = + new JMenuItem("Run " + program.getName() + " on selected features"); + + new_menu.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + if(!checkForSelectionFeatures()) + return; + + final FeatureVector selection_features = + getSelection().getAllFeatures(); + + try + { + final ExternalProgramMonitor monitor = + program.run(selection_features, Splash.getLogger()); + + monitor.addExternalProgramListener(new ExternalProgramListener() + { + public void statusChanged(final ExternalProgramEvent e) + { + if(e.getType() == ExternalProgramEvent.FINISHED) + new MessageFrame(e.getMessage()).setVisible(true); + } + }); + new Thread(monitor).start(); + } + catch(InvalidKeyException e) + { + new MessageDialog(getParentFrame(), + "execution failed: " + e.getMessage()); + } + catch(EntryInformationException e) + { + new MessageDialog(getParentFrame(), + "execution of " + program.getName() + + " failed because: " + e.getMessage()); + } + catch(ReadOnlyException e) + { + new MessageDialog(getParentFrame(), + "execution of " + program.getName() + + " failed because one of the features is " + + "read only"); + } + catch(IOException e) + { + new MessageDialog(getParentFrame(), + "execution of " + program.getName() + + " failed because of an I/O error: " + + e); + } + catch(ExternalProgramException e) + { + new MessageDialog(getParentFrame(), + "execution of " + program.getName() + + " failed: " + e.getMessage()); + } + } + }); + + add(new_menu); + } + + /** + * Make a new options menu item for the given ExternalProgram object. + * @param program Create two menu items for this program. + **/ + private void makeOptionsMenuItem(final ExternalProgram program) + { + if(!(program.getType() == ExternalProgram.AA_PROGRAM || + program.getType() == ExternalProgram.DNA_PROGRAM)) + return; + + final JMenuItem new_options_menu = + new JMenuItem("Set " + program.getName() + " options"); + + new_options_menu.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + new ExternalProgramOptions(program); + } + }); + + add(new_options_menu); + } + + /** + * + **/ + private int[] getIds() + { + final FeatureVector selected_features = getSelection().getAllFeatures(); + final Vector ids_vector = new Vector(); + + for(int feature_index = 0; feature_index < selected_features.size(); + ++feature_index) + { + final Feature this_feature = selected_features.elementAt(feature_index); + + try + { + final Qualifier job_qualifier = + this_feature.getQualifierByName("job"); + + final StringVector values = job_qualifier.getValues(); + + if(values != null && values.size() > 0) + { + for(int value_index=0; value_index<values.size(); + ++value_index) + { + final String job_value = values.elementAt(value_index); + final StringVector bits = StringVector.getStrings(job_value); + + if(bits.size() > 4 && bits.elementAt(2).equals("task:")) + { + try + { + final Integer task_id = Integer.valueOf(bits.elementAt(3)); + + if(!ids_vector.contains(task_id)) + ids_vector.add(task_id); + } + catch(NumberFormatException e) {} + } + } + } + } + catch(InvalidRelationException e) {} + } + + final int[] ids = new int[ids_vector.size()]; + + for(int i=0 ; i<ids.length ; ++i) + ids[i] =((Integer)ids_vector.elementAt(i)).intValue(); + + return ids; + } +} diff --git a/uk/ac/sanger/artemis/components/ScoreChangeEvent.java b/uk/ac/sanger/artemis/components/ScoreChangeEvent.java new file mode 100644 index 000000000..0d567b905 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ScoreChangeEvent.java @@ -0,0 +1,58 @@ +/* ScoreChangeEvent.java + * + * created: Thu Oct 21 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/ScoreChangeEvent.java,v 1.1 2004-06-09 09:47:22 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * The adjustment event emitted when a score changes. + * + * @author Kim Rutherford + * @version $Id: ScoreChangeEvent.java,v 1.1 2004-06-09 09:47:22 tjc Exp $ + **/ + +public class ScoreChangeEvent extends java.util.EventObject { + /** + * Create new ScoreChangeEvent. + **/ + public ScoreChangeEvent (final Object source, final int value) { + super (source); + this.value = value; + } + + /** + * Return the new score value that caused this event (as passed to the + * constructor). + **/ + public int getValue () { + return value; + } + + + /** + * The new score value that caused this event (as passed to the + * constructor). + **/ + final int value; +} diff --git a/uk/ac/sanger/artemis/components/ScoreChangeListener.java b/uk/ac/sanger/artemis/components/ScoreChangeListener.java new file mode 100644 index 000000000..0cde0f0da --- /dev/null +++ b/uk/ac/sanger/artemis/components/ScoreChangeListener.java @@ -0,0 +1,40 @@ +/* ScoreAdjustmentListener.java + * + * created: Thu Oct 21 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/ScoreChangeListener.java,v 1.1 2004-06-09 09:47:28 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * The listener interface for receiving score change events. + * + * @author Kim Rutherford + * @version $Id: ScoreChangeListener.java,v 1.1 2004-06-09 09:47:28 tjc Exp $ + **/ + +public interface ScoreChangeListener { + /** + * Invoked when the value of the score has changed. + **/ + void scoreChanged (final ScoreChangeEvent event); +} diff --git a/uk/ac/sanger/artemis/components/ScoreChanger.java b/uk/ac/sanger/artemis/components/ScoreChanger.java new file mode 100644 index 000000000..1375dd314 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ScoreChanger.java @@ -0,0 +1,222 @@ +/* ScoreChanger.java + * + * created: Thu Oct 21 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/ScoreChanger.java,v 1.1 2004-06-09 09:47:29 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This is a JFrame component that contains ScoreScrollbar components. + * + * @author Kim Rutherford + * @version $Id: ScoreChanger.java,v 1.1 2004-06-09 09:47:29 tjc Exp $ + **/ + +public class ScoreChanger extends JFrame { + /** + * Create a new ScoreChanger. + * @param minimum_value The minimum allowable value for the scroll bars. + * @param maximum_value The maximum allowable value for the scroll bars. + **/ + public ScoreChanger (final String name, + final ScoreChangeListener minimum_listener, + final ScoreChangeListener maximum_listener, + final int minimum_value, final int maximum_value) + throws IllegalArgumentException { + + super (name); + + this.minimum_listener = minimum_listener; + this.maximum_listener = maximum_listener; + this.minimum_value = minimum_value; + this.maximum_value = maximum_value; + + getContentPane ().setLayout (new GridLayout (5, 1)); + + minimum_label = new JLabel (); + + getContentPane ().add (minimum_label); + + minimum_score_scrollbar = + new ScoreScrollbar (minimum_value, maximum_value); + + final ScoreChangeListener this_minimum_listener = + new ScoreChangeListener () { + public void scoreChanged (ScoreChangeEvent event) { + minimum_label.setText ("Minimum Cutoff: " + event.getValue ()); + } + }; + + minimum_score_scrollbar.addScoreChangeListener (this_minimum_listener); + + getContentPane ().add (minimum_score_scrollbar); + + maximum_label = new JLabel (); + + getContentPane ().add (maximum_label); + + maximum_score_scrollbar = + new ScoreScrollbar (minimum_value, maximum_value); + + final ScoreChangeListener this_maximum_listener = + new ScoreChangeListener () { + public void scoreChanged (ScoreChangeEvent event) { + maximum_label.setText ("Maximum Cutoff: " + event.getValue ()); + } + }; + + maximum_score_scrollbar.addScoreChangeListener (this_maximum_listener); + + getContentPane ().add (maximum_score_scrollbar); + + final FlowLayout flow_layout = + new FlowLayout (FlowLayout.CENTER, 18, 0); + + final JPanel button_panel = new JPanel (flow_layout); + + final JButton reset_button = new JButton ("Reset"); + + reset_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + reset (); + } + }); + + button_panel.add (reset_button); + + + final JButton close_button = new JButton ("Close"); + + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + dispose (); + } + }); + + + button_panel.add (close_button); + + getContentPane ().add (button_panel); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + dispose (); + } + }); + + pack (); + + setSize (270, getSize ().height); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + reset (); + + minimum_score_scrollbar.addScoreChangeListener (minimum_listener); + maximum_score_scrollbar.addScoreChangeListener (maximum_listener); + } + + /** + * Call reset (), clean up the listeners then call super.dispose (). + **/ + public void dispose () { + reset (); + + minimum_score_scrollbar.removeScoreChangeListener (minimum_listener); + maximum_score_scrollbar.removeScoreChangeListener (maximum_listener); + + super.dispose (); + } + + /** + * Reset the minimum_score_scrollbar to 0 and the maximum_score_scrollbar + * to 100. + **/ + private void reset () { + minimum_score_scrollbar.setValue (minimum_value); + maximum_score_scrollbar.setValue (maximum_value); + + minimum_label.setText ("Minimum Cutoff: " + minimum_value); + maximum_label.setText ("Maximum Cutoff: " + maximum_value); + + final ScoreChangeEvent minimum_event = + new ScoreChangeEvent (this, minimum_value); + + minimum_listener.scoreChanged (minimum_event); + + final ScoreChangeEvent maximum_event = + new ScoreChangeEvent (this, maximum_value); + + maximum_listener.scoreChanged (maximum_event); + } + + /** + * The ScoreChangeListener for the minimum score that was passed to the + * constructor. + **/ + final ScoreChangeListener minimum_listener; + + /** + * The minimum score scrollbar the created by the constructor. + **/ + final ScoreScrollbar minimum_score_scrollbar; + + /** + * The ScoreChangeListener for the maximum score that was passed to the + * constructor. + **/ + final ScoreChangeListener maximum_listener; + + /** + * The maximum score scrollbar the created by the constructor. + **/ + final ScoreScrollbar maximum_score_scrollbar; + + /** + * The minimum allowable value for the scroll bars. + **/ + final int minimum_value; + + /** + * The maximum allowable value for the scroll bars. + **/ + final int maximum_value; + + /** + * A Label that shows something like this: "Minimum Cutoff: 0" + **/ + final JLabel minimum_label; + + /** + * A Label that shows something like this: "Maximum Cutoff: 100" + **/ + final JLabel maximum_label; +} + diff --git a/uk/ac/sanger/artemis/components/ScoreScrollbar.java b/uk/ac/sanger/artemis/components/ScoreScrollbar.java new file mode 100644 index 000000000..d234bb8cb --- /dev/null +++ b/uk/ac/sanger/artemis/components/ScoreScrollbar.java @@ -0,0 +1,120 @@ +/* ScoreScrollbar.java + * + * created: Thu Oct 21 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1999 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/ScoreScrollbar.java,v 1.1 2004-06-09 09:47:31 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This component is a Scrollbar that generates ScoreChange events when the + * value changes. + * + * @author Kim Rutherford + * @version $Id: ScoreScrollbar.java,v 1.1 2004-06-09 09:47:31 tjc Exp $ + **/ + +public class ScoreScrollbar extends JScrollBar + implements AdjustmentListener { + /** + * Constructs a new horizontal score scroll bar with an initial value of 0. + * @param minimum_value The minimum allowable value for the scroll bar. + * @param maximum_value The maximum allowable value for the scroll bar. + **/ + public ScoreScrollbar (final int minimum_value, final int maximum_value) { + this (Scrollbar.HORIZONTAL, minimum_value, minimum_value, maximum_value); + } + + /** + * Constructs a new score scroll bar with the specified orientation. + * + * The orientation argument must take one of the two values + * java.awt.Scrollbar.HORIZONTAL, or java.awt.Scrollbar.VERTICAL, + * indicating a horizontal or vertical scroll bar, respectively. + * @param orientation indicates the orientation of the scroll bar. + * @param value The initial value of the scrollbar. + * @param minimum_value The minimum allowable value for the scroll bar. + * @param maximum_value The maximum allowable value for the scroll bar. + * @exception IllegalArgumentException when an illegal value for the + * orientation argument is supplied or if the value parameter is less + * than minimum_value or greater than maximum_value. + **/ + public ScoreScrollbar (final int orientation, final int value, + final int minimum_value, final int maximum_value) + throws IllegalArgumentException { + super (orientation, + (value < minimum_value || value > maximum_value ? + minimum_value : + value), + 1, minimum_value, maximum_value + 1); + } + + /** + * Add the given ScoreChangeListener as a listener for ScoreChange events + * from this components. + **/ + public void addScoreChangeListener (final ScoreChangeListener l) { + score_change_listeners.addElement (l); + + if (score_change_listeners.size () == 1) { + addAdjustmentListener (this); + } + } + + /** + * Remove the given ScoreChangeListener as a listener for ScoreChange + * events from this components. + **/ + public void removeScoreChangeListener (final ScoreChangeListener l) { + score_change_listeners.addElement (l); + + if (score_change_listeners.size () == 0) { + removeAdjustmentListener (this); + } + } + + /** + * Implementation of the AdjustmentListener interface. + **/ + public void adjustmentValueChanged (AdjustmentEvent e) { + for (int i = 0 ; i < score_change_listeners.size () ; ++i) { + final ScoreChangeEvent event = + new ScoreChangeEvent (this, getValue ()); + final ScoreChangeListener this_listener = + (ScoreChangeListener)(score_change_listeners.elementAt (i)); + this_listener.scoreChanged (event); + } + } + + /** + * The ScoreChangeListener objects that have been added with + * addScoreChangeListener (). + **/ + private final java.util.Vector score_change_listeners = + new java.util.Vector (); +} + diff --git a/uk/ac/sanger/artemis/components/SearchResultViewer.java b/uk/ac/sanger/artemis/components/SearchResultViewer.java new file mode 100644 index 000000000..17228e217 --- /dev/null +++ b/uk/ac/sanger/artemis/components/SearchResultViewer.java @@ -0,0 +1,108 @@ +/* SearchResultViewer.java + * + * created: Thu Feb 24 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SearchResultViewer.java,v 1.1 2004-06-09 09:47:34 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; + +import uk.ac.sanger.artemis.util.*; + +import java.io.*; +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A component that displays the results of external searches, with the + * ability to send the results to a netscape process. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: SearchResultViewer.java,v 1.1 2004-06-09 09:47:34 tjc Exp $ + **/ + +public class SearchResultViewer extends FileViewer { + /** + * Create a new SearchResultViewer component. + * @param title The name to attach to the new JFrame. + * @param file_name The file to read into the new viewer. + **/ + public SearchResultViewer (final String label, + final Document document) + throws IOException { + + super (label); + + try { + readFile (document.getReader ()); + } catch (IOException e) { + System.out.println ("error while reading results: " + e); + dispose (); + throw e; + } + + if (!Options.getOptions ().getPropertyTruthValue ("sanger_options")) { + return; + } + + final JButton to_browser = new JButton ("Send to browser"); + + getButtonPanel ().add (to_browser); + + to_browser.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + try { + sendToBrowser (document.toString ()); + } catch (IOException e) { + System.out.println ("error while reading results: " + e); + new MessageDialog (SearchResultViewer.this, + "Message", + "Send to browser failed: " + e); + } catch (ExternalProgramException e) { + System.out.println ("error while reading results: " + e); + new MessageDialog (SearchResultViewer.this, + "Message", + "Send to browser failed: " + e); + } + } + }); + } + + /** + * Mark up the contents of the given file (which should contain blast or + * fasta output) and send it to a web browser (with netscape -remote). + **/ + public static void sendToBrowser (final String file_name) + throws IOException, ExternalProgramException { + final String [] arguments = { + file_name + }; + + final Process process = + ExternalProgram.startProgram ("../etc/results_to_netscape", arguments); + + new ProcessWatcher (process, "results_to_netscape", false); + } +} diff --git a/uk/ac/sanger/artemis/components/SelectMenu.java b/uk/ac/sanger/artemis/components/SelectMenu.java new file mode 100644 index 000000000..371c54185 --- /dev/null +++ b/uk/ac/sanger/artemis/components/SelectMenu.java @@ -0,0 +1,827 @@ +/* SelectMenu.java + * + * created: Thu Jan 14 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SelectMenu.java,v 1.1 2004-06-09 09:47:37 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.OutOfRangeException; +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.io.Range; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.KeyVector; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.InvalidKeyException; +import uk.ac.sanger.artemis.io.InvalidRelationException; +import uk.ac.sanger.artemis.io.EntryInformation; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This menu has contains items such a "Select all", "Select none" and + * "Select by key". + * + * @author Kim Rutherford + * @version $Id: SelectMenu.java,v 1.1 2004-06-09 09:47:37 tjc Exp $ + **/ + +public class SelectMenu extends SelectionMenu { + /** + * Create a new SelectMenu object and all it's menu items. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param entry_group The EntryGroup object where features and exons are + * selected from. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + * @param menu_name The name of the new menu. + **/ + public SelectMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group, + final String menu_name) { + super (frame, menu_name, selection); + + this.entry_group = entry_group; + this.goto_event_source = goto_event_source; + + selector_item = new JMenuItem ("Feature Selector ..."); + selector_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + new Selector (selection, goto_event_source, getEntryGroup (), + base_plot_group); + } + }); + + add (selector_item); + + addSeparator (); + + all_item = new JMenuItem ("All"); + all_item.setAccelerator (SELECT_ALL_KEY); + all_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectAll (); + } + }); + + add (all_item); + + all_bases_item = new JMenuItem ("All Bases"); + all_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectAllBases (); + } + }); + + add (all_bases_item); + + none_item = new JMenuItem ("None"); + none_item.setAccelerator (SELECT_NONE_KEY); + none_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + clearSelection (); + } + }); + + add (none_item); + + select_by_key_item = new JMenuItem ("By Key"); + select_by_key_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectByKeyPopup (); + } + }); + + add (select_by_key_item); + + select_non_pseudo_cds_item = + new JMenuItem ("CDS Features without /pseudo"); + select_non_pseudo_cds_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + // select all CDS features that do not have the /pseudo qualifier + final FeatureKeyQualifierPredicate predicate = + new FeatureKeyQualifierPredicate (Key.CDS, "pseudo", false); + + clearSelection (); + + selectFeaturesByPredicate (predicate); + } + }); + + add (select_non_pseudo_cds_item); + + select_all_cds_item = new JMenuItem ("All CDS Features"); + select_all_cds_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + final FeatureKeyPredicate predicate = + new FeatureKeyPredicate (Key.CDS); + + clearSelection (); + + selectFeaturesByPredicate (predicate); + } + }); + + add (select_all_cds_item); + + select_same_type_item = new JMenuItem ("Same Key"); + select_same_type_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectSameKey (); + } + }); + + add (select_same_type_item); + + select_matching_qualifiers_item = + new JMenuItem ("Features Matching Qualifier"); + select_matching_qualifiers_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectMatchingQualifiers (); + } + }); + + add (select_matching_qualifiers_item); + + select_orf_item = new JMenuItem ("Open Reading Frame"); + select_orf_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectOrf (); + } + }); + + add (select_orf_item); + + select_features_in_range_item = + new JMenuItem ("Features Overlapping Selection"); + select_features_in_range_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectOverlappingFeatures (); + } + }); + + add (select_features_in_range_item); + + select_base_range_item = new JMenuItem ("Base Range ..."); + select_base_range_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectBaseRange (); + } + }); + + add (select_base_range_item); + + select_aa_range_in_feature_item = new JMenuItem ("Feature AA Range ..."); + select_aa_range_in_feature_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + selectFeatureAARange (); + } + }); + + add (select_aa_range_in_feature_item); + + addSeparator (); + + selection_toggle_item = new JMenuItem ("Toggle Selection"); + selection_toggle_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + toggleSelection (); + } + }); + + add (selection_toggle_item); + } + + /** + * The shortcut for Select All + **/ + final static KeyStroke SELECT_ALL_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_A, InputEvent.CTRL_MASK); + + /** + * The shortcut for Select None + **/ + final static KeyStroke SELECT_NONE_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_N, InputEvent.CTRL_MASK); + + /** + * Create a new SelectMenu object and all it's menu items. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param goto_event_source The object the we will call makeBaseVisible () + * on. + * @param entry_group The EntryGroup object where features and exons are + * selected from. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public SelectMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group) { + this (frame, selection, goto_event_source, entry_group, + base_plot_group, "Select"); + } + + /** + * Warn if the user tries to select more than this number of features. + **/ + final private int MAX_FEATURE_TO_SELECT_COUNT = 10000; + + /** + * Select all the features in the active entries. If there are more than + * 1000 features the user is asked for confirmation. + **/ + private void selectAll () { + if (getEntryGroup ().getAllFeaturesCount () > + MAX_FEATURE_TO_SELECT_COUNT) { + final YesNoDialog dialog = + new YesNoDialog (getParentFrame (), + "Are you sure you want to select " + + getEntryGroup ().getAllFeaturesCount () + + " features?"); + + if (!dialog.getResult ()) { + return; + } + } + + final FeatureVector all_features = getEntryGroup ().getAllFeatures (); + + if (getEntryGroup () instanceof SimpleEntryGroup) { + // special case for speed + getSelection ().set (all_features); + } else { + clearSelection (); + + getSelection ().add (all_features); + } + } + + /** + * Select all the bases. + **/ + private void selectAllBases () { + try { + final Strand strand = getEntryGroup ().getBases ().getForwardStrand (); + + final MarkerRange new_range = + strand.makeMarkerRangeFromPositions (1, strand.getSequenceLength ()); + + getSelection ().setMarkerRange (new_range); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + + /** + * Remove the all Features and FeatureSegments in the EntryGroup from the + * Selection. If the current EntryGroup is a SimpleEntryGroup then this + * method just calls getSelection ().clear (); + **/ + private void clearSelection () { + if (getEntryGroup () instanceof SimpleEntryGroup) { + // special case for speed + getSelection ().clear (); + } else { + getSelection ().setMarkerRange (null); + + final FeatureEnumeration test_enumerator = getEntryGroup ().features (); + + while (test_enumerator.hasMoreFeatures ()) { + final Feature this_feature = test_enumerator.nextFeature (); + + getSelection ().remove (this_feature); + getSelection ().removeSegmentsOf (this_feature); + } + } + } + + /** + * Invert the selection - after calling this method the selection will + * contain only those features that were not in the selection before the + * call. + **/ + private void toggleSelection () { + final FeatureVector selected_features = getSelection ().getAllFeatures (); + + final FeatureVector all_features = getEntryGroup ().getAllFeatures (); + + final FeatureVector new_selection = new FeatureVector (); + + for (int i = 0 ; i < all_features.size () ; ++i) { + final Feature this_feature = all_features.elementAt (i); + + if (!selected_features.contains (this_feature)) { + new_selection.add (this_feature); + } + } + + clearSelection (); + + getSelection ().add (new_selection); + } + + + /** + * Popup a TextRequester component and then select all the features that + * have the same key as the user types. + **/ + private void selectByKeyPopup () { + final KeyChooser key_chooser; + + final EntryInformation default_entry_information = + Options.getArtemisEntryInformation (); + + key_chooser = new KeyChooser (default_entry_information, + new Key ("misc_feature")); + + key_chooser.getKeyChoice ().addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + selectByKey (key_chooser.getKeyChoice ().getSelectedItem ()); + key_chooser.setVisible (false); + key_chooser.dispose (); + } + }); + + key_chooser.getOKButton ().addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + selectByKey (key_chooser.getKeyChoice ().getSelectedItem ()); + key_chooser.setVisible (false); + key_chooser.dispose (); + } + }); + + key_chooser.setVisible (true); + } + + /** + * Select all the features that have the given key. + **/ + private void selectByKey (final Key key) { + final FeaturePredicate predicate = new FeatureKeyPredicate (key); + + selectFeaturesByPredicate (predicate); + } + + /** + * Select all the features that have the given key and contains a qualifier + * with the given name and value. + * If key is null select any feature with the contains a qualifier with the + * given name and value. + **/ + private void selectByQualifier (final Key key, + final String name, final String value) { + final FeaturePredicate predicate = + new FeatureKeyQualifierPredicate (key, name, value); + + selectFeaturesByPredicate (predicate); + } + + /** + * Select all the features that have the same key as the currently selected + * features. + **/ + private void selectSameKey () { + if (! checkForSelectionFeatures ()) { + return; + } + + final KeyVector seen_keys = new KeyVector (); + + final FeatureVector selected_features = getSelection ().getAllFeatures (); + + for (int i = 0 ; i < selected_features.size () ; ++i) { + final Feature current_feature = selected_features.elementAt (i); + + if (!seen_keys.contains (current_feature.getKey ())) { + seen_keys.add (current_feature.getKey ()); + } + } + + clearSelection (); + + for (int i = 0 ; i < seen_keys.size () ; ++i) { + selectByKey (seen_keys.elementAt (i)); + } + } + + /** + * Ask the user for a qualifier name, list all of the qualifier with that + * name in the currently selected feature then select all features that + * have that name and qualifier. + **/ + private void selectMatchingQualifiers () { + if (!checkForSelectionFeatures (getParentFrame (), getSelection ())) { + return; + } + + final FeatureVector selected_features = getSelection ().getAllFeatures (); + + if (selected_features.size () > 1) { + new MessageDialog (getParentFrame (), "select only one feature"); + return; + } + + final Feature selected_feature = selected_features.elementAt (0); + + final StringVector qualifier_names = + Feature.getAllQualifierNames (selected_features); + + if (qualifier_names.size () == 0) { + new MessageDialog (getParentFrame (), "feature has no qualifiers"); + return; + } + + final ChoiceFrame name_choice_frame = + new ChoiceFrame ("Select a qualifier name ...", qualifier_names); + + final JComboBox name_choice = name_choice_frame.getChoice (); + + class SelectListener implements ActionListener, ItemListener { + public void doStuff () { + selectMatchingQualifiersHelper (selected_feature, + (String) name_choice.getSelectedItem ()); + name_choice_frame.setVisible (false); + name_choice_frame.dispose (); + } + + public void itemStateChanged (ItemEvent _) { + doStuff (); + // need to remove() because itemStateChanged() is called twice on the + // Tru64 1.4.1 JVM + name_choice.removeItemListener (this); + } + + public void actionPerformed (ActionEvent _) { + doStuff (); + name_choice_frame.getOKButton ().removeActionListener (this); + } + }; + + final SelectListener listener = new SelectListener (); + + name_choice.addItemListener (listener); + name_choice_frame.getOKButton ().addActionListener (listener); + + name_choice_frame.setVisible (true); + } + + /** + * Pop up a list of the values of the qualifiers with the given name in the + * given feature then select all features that have a qualifier with the + * same and value. + **/ + private void selectMatchingQualifiersHelper (final Feature feature, + final String name) { + final Qualifier qualifier; + + try { + qualifier = feature.getQualifierByName (name); + } catch (InvalidRelationException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + if (qualifier == null) { + throw new Error ("internal error - unexpected null reference"); + } + + final StringVector qualifier_values = qualifier.getValues (); + + if (qualifier_values.size () == 0) { + new MessageDialog (getParentFrame (), + "qualifier " + name + " has no values in "); + return; + } + + if (qualifier_values.size () == 1) { + selectByQualifier (null, name, qualifier_values.elementAt (0)); + } else { + final ChoiceFrame value_choice_frame = + new ChoiceFrame ("Select a qualifier value", qualifier_values); + + final JComboBox value_choice = value_choice_frame.getChoice (); + + value_choice.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + selectByQualifier (null, name, + (String) value_choice.getSelectedItem ()); + value_choice_frame.setVisible (false); + value_choice_frame.dispose (); + } + }); + + value_choice_frame.getOKButton ().addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + selectByQualifier (null, name, + (String) value_choice.getSelectedItem ()); + value_choice_frame.setVisible (false); + value_choice_frame.dispose (); + } + }); + + value_choice_frame.setVisible (true); + } + } + + /** + * Select all the features that match the given predicate. + **/ + private void selectFeaturesByPredicate (final FeaturePredicate predicate) { + final FeatureVector new_selection_features = new FeatureVector (); + + final FeatureEnumeration feature_enum = getEntryGroup ().features (); + + while (feature_enum.hasMoreFeatures ()) { + final Feature current_feature = feature_enum.nextFeature (); + + if (predicate.testPredicate (current_feature)) { + new_selection_features.add (current_feature); + } + } + + clearSelection (); + + getSelection ().add (new_selection_features); + } + + /** + * If a range of bases is selected, then select the ORF around those + * bases. + **/ + private void selectOrf () { + if (!checkForSelectionRange ()) { + return; + } + + final MarkerRange range = getSelection ().getMarkerRange (); + + final Strand strand = getEntryGroup ().getBases ().getForwardStrand (); + + final MarkerRange start_orf_range = + strand.getORFAroundMarker (range.getStart (), true); + + /*final*/ Marker end_marker; + + // get the marker of the first base of the last codon in the range (we + // want to keep in the same frame) + try { + final int start_end_diff = range.getCount () - 1; + + final int mod_length = start_end_diff - start_end_diff % 3; + + end_marker = range.getStart ().moveBy (mod_length); + } catch (OutOfRangeException e) { + end_marker = range.getStart (); + } + + final MarkerRange end_orf_range = + strand.getORFAroundMarker (end_marker, true); + + MarkerRange new_range = range; + + if (start_orf_range != null) { + new_range = new_range.extendRange (start_orf_range); + } + + if (end_orf_range != null) { + new_range = new_range.extendRange (end_orf_range); + } + + if (start_orf_range == null && end_orf_range == null && + range.getCount () <= 6) { // 6 == two codons + new MessageDialog (getParentFrame (), + "there is no open reading frame at the selected " + + "bases"); + return; + } + + getSelection ().setMarkerRange (new_range); + } + + /** + * This method will ask the user for a range of bases (such as 100..200), + * using a MarkerRangeRequester component, then selects that range. + **/ + private void selectBaseRange () { + final MarkerRangeRequester range_requester = + new MarkerRangeRequester ("enter a range of bases (eg. 100..200):", + 18, ""); + + final MarkerRangeRequesterListener listener = + new MarkerRangeRequesterListener () { + public void actionPerformed (final MarkerRangeRequesterEvent event) { + final MarkerRange marker_range = + event.getMarkerRange (getEntryGroup ().getBases ()); + + getSelection ().setMarkerRange (marker_range); + + makeBaseVisible (getSelection ().getStartBaseOfSelection ()); + } + }; + + range_requester.addMarkerRangeRequesterListener (listener); + + range_requester.setVisible (true); + } + + /** + * This method will ask the user for a range of amino acids in a feature + * (such as 100..200), using a Requester component, then selects that + * range on the sequence. + **/ + private void selectFeatureAARange () { + if (!checkForSelectionFeatures (getParentFrame (), getSelection ())) { + return; + } + + final FeatureVector selected_features = getSelection ().getAllFeatures (); + + if (selected_features.size () > 1) { + new MessageDialog (getParentFrame (), "select only one feature"); + return; + } + + final Feature selected_feature = selected_features.elementAt (0); + + final MarkerRangeRequester range_requester = + new MarkerRangeRequester ("enter a range of amino acids (eg. 100..200):", + 18, ""); + + final MarkerRangeRequesterListener listener = + new MarkerRangeRequesterListener () { + public void actionPerformed (final MarkerRangeRequesterEvent event) { + final Range range = event.getRawRange (); + + if (range == null) { + return; + } + + final int start_position = range.getStart (); + final int end_position = range.getEnd (); + + final int start_pos_in_feature = (start_position - 1) * 3 + 1; + final int end_pos_in_feature = (end_position - 1) * 3 + 3; + + try { + final Marker start_marker = + selected_feature.getPositionInSequence (start_pos_in_feature); + + final Marker end_marker = + selected_feature.getPositionInSequence (end_pos_in_feature); + + final MarkerRange marker_range = + new MarkerRange (start_marker.getStrand (), + start_marker.getPosition (), + end_marker.getPosition ()); + + getSelection ().setMarkerRange (marker_range); + + makeBaseVisible (getSelection ().getStartBaseOfSelection ()); + } catch (OutOfRangeException e) { + new MessageDialog (getParentFrame (), + "amino acid range is out of range " + + "for this feature"); + } + } + }; + + range_requester.addMarkerRangeRequesterListener (listener); + + range_requester.setVisible (true); + } + + /** + * If there are some selected bases, select the feature in that range + * (replacing the orginal selection). If some features are selected, + * select those features that overlap the selected features (and unselect + * the original features). + **/ + private void selectOverlappingFeatures () { + final MarkerRange selected_marker_range = + getSelection ().getMarkerRange (); + + if (selected_marker_range == null) { + final FeatureVector selected_features = + getSelection ().getAllFeatures (); + + if (selected_features.size () == 0) { + new MessageDialog (getParentFrame (), "nothing selected"); + return; + } else { + final FeatureVector new_selection = new FeatureVector (); + + for (int i = 0 ; i < selected_features.size () ; ++i) { + final Feature this_feature = selected_features.elementAt (i); + + final Range this_feature_raw_range = this_feature.getMaxRawRange (); + + try { + final FeatureVector overlapping_features = + getEntryGroup ().getFeaturesInRange (this_feature_raw_range); + + for (int overlapping_feature_index = 0 ; + overlapping_feature_index < overlapping_features.size () ; + ++overlapping_feature_index) { + final Feature overlapping_feature = + overlapping_features.elementAt (overlapping_feature_index); + + if (!new_selection.contains (overlapping_feature)) { + new_selection.add (overlapping_feature); + } + } + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + + for (int i = 0 ; i < selected_features.size () ; ++i) { + new_selection.remove (selected_features.elementAt (i)); + } + + getSelection ().set (new_selection); + } + } else { + try { + final Range raw_range = selected_marker_range.getRawRange (); + getSelection ().set (getEntryGroup ().getFeaturesInRange (raw_range)); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + } + } + + /** + * This method sends an GotoEvent to all the GotoEvent listeners that will + * make the given base visible. + **/ + private void makeBaseVisible (final Marker base_marker) { + goto_event_source.gotoBase (base_marker); + } + + /** + * Return the EntryGroup that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + /** + * The EntryGroup object that was passed to the constructor. + **/ + private EntryGroup entry_group; + + /** + * The GotoEventSource object that was passed to the constructor. + **/ + private GotoEventSource goto_event_source; + + private JMenuItem selector_item = null; + private JMenuItem all_item = null; + private JMenuItem all_bases_item = null; + private JMenuItem none_item = null; + private JMenuItem selection_toggle_item = null; + private JMenuItem select_same_type_item = null; + private JMenuItem select_matching_qualifiers_item = null; + private JMenuItem select_features_in_range_item = null; + private JMenuItem select_base_range_item = null; + private JMenuItem select_aa_range_in_feature_item = null; + private JMenuItem select_orf_item = null; + private JMenuItem select_by_key_item = null; + private JMenuItem select_non_pseudo_cds_item = null; + private JMenuItem select_all_cds_item = null; +} diff --git a/uk/ac/sanger/artemis/components/SelectionInfoDisplay.java b/uk/ac/sanger/artemis/components/SelectionInfoDisplay.java new file mode 100644 index 000000000..d21cde661 --- /dev/null +++ b/uk/ac/sanger/artemis/components/SelectionInfoDisplay.java @@ -0,0 +1,435 @@ +/* SelectionInfoDisplay.java + * + * created: Tue Dec 15 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000,2002 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/SelectionInfoDisplay.java,v 1.1 2004-06-09 09:47:40 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.io.StreamQualifier; +import uk.ac.sanger.artemis.io.QualifierInfo; +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.io.Qualifier; +import uk.ac.sanger.artemis.io.QualifierVector; +import uk.ac.sanger.artemis.io.Range; +import uk.ac.sanger.artemis.util.OutOfRangeException; + +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * This class displays information about the selection in a Label. + * + * @author Kim Rutherford + * @version $Id: SelectionInfoDisplay.java,v 1.1 2004-06-09 09:47:40 tjc Exp $ + **/ +public class SelectionInfoDisplay extends CanvasPanel + implements SelectionChangeListener { + /** + * Create a new SelectionInfoDisplay component. + * @param entry_group The EntryGroup that this component will display. + * @param selection The Selection that this component will display. + **/ + public SelectionInfoDisplay (final EntryGroup entry_group, + final Selection selection) { + this.entry_group = entry_group; + this.selection = selection; + + getSelection ().addSelectionChangeListener (this); + + getCanvas ().addComponentListener (new ComponentAdapter () { + public void componentResized(ComponentEvent e) { + getCanvas ().setSize (getCanvas ().getSize ().width, + getFontHeight () + 1); + getCanvas ().repaint (); + } + public void componentShown (ComponentEvent e) { + getCanvas ().setSize (getCanvas ().getSize ().width, + getFontHeight () + 1); + getCanvas ().repaint (); + } + }); + + int canvas_width = getCanvas ().getSize ().width; + + setBackground (Color.white); + + setSize (100, getFontHeight ()); + } + + /** + * + **/ + public Dimension getPreferredSize () { + return new Dimension (10, getFontHeight ()); + } + + /** + * + **/ + public Dimension getMinimumSize () { + return new Dimension (10, getFontHeight ()); + } + + /** + * Implementation of the SelectionChangeListener interface. We listen to + * SelectionChange events so that we can update the list to reflect the + * current selection. + **/ + public void selectionChanged (SelectionChangeEvent event) { + repaintCanvas (); + } + + /** + * Draw the label. + **/ + public void paintCanvas (final Graphics g) { + if (!isVisible ()) { + return; + } + + final FeatureVector features = getSelection ().getAllFeatures (); + + final StringBuffer new_text = new StringBuffer (); + + new_text.append (markerRangeText (getSelection (), entry_group)); + + int base_total = 0; + int aa_total = 0; + boolean saw_a_non_cds = false; + + if (features.size () > 0) { + final StringBuffer feature_names = new StringBuffer (); + + if (features.size () < 100) { + if (features.size () > 1) { + feature_names.append (" ("); + } + + // show up to 10 features + for (int i = 0 ; i < features.size () ; ++i) { + final Feature current_feature = features.elementAt (i); + + base_total += current_feature.getBaseCount (); + + aa_total += current_feature.getAACount (); + + if (!current_feature.getKey ().equals ("CDS")) { + saw_a_non_cds = true; + } + + if (i < 10) { + if (i != 0) { + feature_names.append (' '); + } + feature_names.append (current_feature.getIDString ()); + } + } + + if (features.size () > 10) { + feature_names.append ("..."); + } + + if (features.size () == 1) { + // only one feature so append some qualifiers + feature_names.append (" ("); + feature_names.append (getQualifierString (features.elementAt (0))); + } + feature_names.append (")"); + + } + + if (features.size () == 1) { + new_text.append ("Selected feature: "); + } else { + new_text.append (features.size () + " selected features "); + } + + if (features.size () < 100) { + // show a count of the number of bases and amino acids in the selected + // feature + if (features.size () > 1) { + new_text.append ("total bases " + base_total); + + if (!saw_a_non_cds) { + new_text.append (" total amino acids " + aa_total); + } + } else { + if (features.size () == 1) { + new_text.append ("bases " + base_total); + + if (!saw_a_non_cds) { + new_text.append (" amino acids " + aa_total); + } + } + } + } + + new_text.append (" "); + + new_text.append (feature_names); + } + + + String text = new_text.toString (); + + if (text.length () > 0) { + if (text.length () > 150) { + // call substring () just to keep the String a sensible length + text = text.substring (0, 150); + } + } else { + text = "Nothing selected"; + } + + g.setColor (new Color (230, 230, 230)); + + g.fillRect (0, 0, getCanvasWidth (), getCanvasHeight ()); + + g.setColor (Color.black); + + g.drawString (text, 2, getFontMaxAscent () + 1); + } + + /** + * Return a String containing the qualifiers (except the /note) of given + * feature. + **/ + private String getQualifierString (final Feature feature) { + final QualifierVector qualifiers = feature.getQualifiers (); + + // if we see a /note or /fasta_file it gets saved for later + final Vector saved_qualifiers = new Vector (); + + final StringBuffer string_buffer = new StringBuffer (); + + final EntryInformation entry_information = + feature.getEntry ().getEntryInformation (); + + for (int i = 0 ; i < qualifiers.size () ; ++i) { + final Qualifier this_qualifier = qualifiers.elementAt (i); + + if (this_qualifier.getName ().equals ("note") || + this_qualifier.getName ().endsWith ("_file")) { + // save the qualifier and put it last + saved_qualifiers.addElement (this_qualifier); + continue; + } + + if (string_buffer.length () > 0) { + // put spaces between the qualifiers + string_buffer.append (' '); + } + + final QualifierInfo qualifier_info = + entry_information.getQualifierInfo (this_qualifier.getName ()); + + string_buffer.append (StreamQualifier.toString (qualifier_info, + this_qualifier)); + } + + for (int i = 0 ; i < saved_qualifiers.size () ; ++i) { + if (string_buffer.length () > 0) { + // put spaces between the qualifiers + string_buffer.append (' '); + } + + final Qualifier this_qualifier = + (Qualifier) saved_qualifiers.elementAt (i); + + final QualifierInfo qualifier_info = + entry_information.getQualifierInfo (this_qualifier.getName ()); + + string_buffer.append (StreamQualifier.toString (qualifier_info, + this_qualifier)); + } + + return string_buffer.toString (); + } + + /** + * If the selection contains a MarkerRange (a range of bases) then return a + * String in this form: "Selected Bases on forward strand: <start>..<end> ", + * otherwise return "". This method is also used by the SelectionViewer + * class. + * @param current_selection The selection to summarize. + * @param entry_group The EntryGroup that the selection refers to. + **/ + static String markerRangeText (final Selection current_selection, + final EntryGroup entry_group) { + final MarkerRange marker_range = current_selection.getMarkerRange (); + + if (marker_range == null) { + return ""; + } else { + final StringBuffer buffer = new StringBuffer (); + + final int start_pos = marker_range.getStart ().getPosition (); + final int end_pos = marker_range.getEnd ().getPosition (); + + if (marker_range.getStrand ().isForwardStrand ()) { + if (start_pos == end_pos) { + buffer.append ("One selected base on forward strand: " + + start_pos + " "); + } else { + buffer.append ((end_pos - start_pos + 1) + + " selected bases on forward strand: " + + start_pos + ".." + end_pos + " "); + } + } else { + if (start_pos == end_pos) { + buffer.append ("One selected base on reverse strand: " + + marker_range.getStart ().getPosition () + + " = complement (" + + marker_range.getEnd ().getRawPosition () + ") "); + } else { + buffer.append ((end_pos - start_pos + 1) + + " selected bases on reverse strand: " + + marker_range.getStart ().getPosition () + ".." + + marker_range.getEnd ().getPosition () + + " = complement (" + + marker_range.getEnd ().getRawPosition () + ".." + + marker_range.getStart ().getRawPosition () + ") "); + } + } + + if (marker_range.getCount () >= 3) { + // check if this codon is in a feature + + final Range raw_range = marker_range.getRawRange (); + + final FeatureVector features; + + try { + features = entry_group.getFeaturesInRange (raw_range); + } catch (OutOfRangeException e) { + return "illegal marker range"; + } + + for (int i = 0 ; i < features.size () ; ++i) { + final Feature this_feature = features.elementAt (i); + + if (!this_feature.isProteinFeature ()) { + // only display protein features + continue; + } + + if (marker_range.isForwardMarker () != + this_feature.isForwardFeature ()) { + // only display feature positions in features on the same strand + // as the selected bases + continue; + } + + final String this_feature_id = this_feature.getIDString (); + + final Marker start_marker = marker_range.getStart (); + + final Marker end_marker = marker_range.getEnd (); + + final int start_position_in_feature = + this_feature.getFeaturePositionFromMarker (start_marker); + + final int start_codon_position = + start_position_in_feature - this_feature.getCodonStart () + 1; + + String start_string = null; + + if (start_position_in_feature != -1) { + if (start_codon_position % 3 == 0) { + start_string = "codon " + (start_codon_position / 3 + 1); + } + } + + String end_string = null; + + final int end_position_in_feature = + this_feature.getFeaturePositionFromMarker (end_marker); + + final int end_codon_position = + end_position_in_feature - this_feature.getCodonStart () - 1; + + if (end_position_in_feature != -1) { + if (start_codon_position != end_codon_position && + end_codon_position % 3 == 0) { + end_string = "codon " + (end_codon_position / 3 + 1); + } + } + + if (!(start_string == null && end_string == null)) { + buffer.append (" ("); + + if (start_string != null) { + buffer.append (start_string); + } + + if (start_string != null && end_string != null) { + buffer.append (" to "); + } + + if (end_string != null) { + buffer.append (end_string); + } + + buffer.append (" in feature " + this_feature.getIDString () + + ") "); + } + } + } + + return buffer.append (" ").toString (); + } + } + + /** + * Return the Selection reference that was passed to the constructor. + **/ + private Selection getSelection () { + return selection; + } + + /** + * Return the Selection object that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + /** + * The reference of the EntryGroup object that was passed to the + * constructor. + **/ + private EntryGroup entry_group; + + /** + * This is a reference to the Selection object that created this + * component. + **/ + private final Selection selection; +} + diff --git a/uk/ac/sanger/artemis/components/SelectionMenu.java b/uk/ac/sanger/artemis/components/SelectionMenu.java new file mode 100644 index 000000000..252bd30e3 --- /dev/null +++ b/uk/ac/sanger/artemis/components/SelectionMenu.java @@ -0,0 +1,294 @@ +/* SelectionMenu.java + * + * created: Sun Jan 10 1999 + * + * This file is part of Artemis + * + * Copyright(C) 1998,1999,2000,2001,2002 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/SelectionMenu.java,v 1.1 2004-06-09 09:47:44 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.sequence.MarkerRange; +import uk.ac.sanger.artemis.*; + +import java.awt.event.*; +import javax.swing.*; + +/** + * This a super class for EditMenu, ViewMenu and GotoMenu. It is a JMenu that + * knows how to get hold of the Selection. It also has the method + * getParentFrame() to find the owning JFrame of the menu. + * + * @author Kim Rutherford + * @version $Id: SelectionMenu.java,v 1.1 2004-06-09 09:47:44 tjc Exp $ + **/ + +public class SelectionMenu extends JMenu +{ + + /** The Selection that was passed to the constructor. */ + /* final */ private Selection selection; + + /** The JFrame reference that was passed to the constructor. */ + private JFrame frame = null; + + /** + * Create a new SelectionMenu object. + * @param frame The JFrame that owns this JMenu. + * @param name The string to use as the menu title. + * @param selection The Selection that the commands in the menu will + * operate on. + **/ + public SelectionMenu(final JFrame frame, + final String menu_name, + final Selection selection) + { + super(menu_name); + this.frame = frame; + this.selection = selection; + } + + /** + * Return the JFrame that was passed to the constructor. + **/ + public JFrame getParentFrame() + { + return frame; + } + + /** + * Check that the are some Features in the current selection. If there are + * some features then return true. If there are no features selected then + * popup a message saying so and return false. + **/ + protected boolean checkForSelectionFeatures() + { + return checkForSelectionFeatures(getParentFrame(), getSelection()); + } + + /** + * Check that the are some Features in the given selection. If there are + * some features then return true. If there are no features selected then + * popup a message saying so and return false. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The selected features to check. + **/ + static boolean checkForSelectionFeatures(final JFrame frame, + final Selection selection) + { + final FeatureVector features_to_check = selection.getAllFeatures(); + + if(features_to_check.size() == 0) + { + new MessageDialog(frame, "No features selected"); + return false; + } + else + return true; + } + + /** + * Check that the are some Features in the current selection and that the + * selection isn't too big. If there is more than one feature in the + * selection and less than the given maximum then return true. If there + * are no features selected then popup a message saying so and return + * false. If there are more selected features than the given maximum + * than display the message in a YesNoDialog component and return the + * result of the dialog. + **/ + protected boolean checkForSelectionFeatures(final int maximum_size, + final String message) + { + return checkForSelectionFeatures(getParentFrame(), getSelection(), + maximum_size, message); + } + + /** + * Check that the are some Features in the given selection and that the + * selection isn't too big. If there is more than one feature in the + * selection and less than the given maximum then return true. If there + * are no features selected then popup a message saying so and return + * false. If there are more selected features than the given maximum + * than display the message in a YesNoDialog component and return the + * result of the dialog. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The selected features to check. + **/ + static boolean checkForSelectionFeatures(final JFrame frame, + final Selection selection, + final int maximum_size, + final String message) + { + final FeatureVector features_to_check = selection.getAllFeatures(); + + if(features_to_check.size() == 0) + { + new MessageDialog(frame, "No features selected"); + return false; + } + else + { + if(features_to_check.size() > maximum_size) + { + final YesNoDialog dialog = + new YesNoDialog(frame, message); + + return dialog.getResult(); + } + else + return true; + } + } + + /** + + * Check that there are only CDS features in the given selection, that there + * are some features selected and that the selection isn't too big. If + * there is more than one feature in the selection and less than the given + * maximum then return true. If there are no features selected then popup + * a message saying so and return false. If there are more selected + * features than the given maximum than display the message in a + * YesNoDialog component and return the result of the dialog. + * @param frame The Frame to use for MessageDialog components. + * @param selection The selected features to check. + **/ + static boolean checkForSelectionCDSFeatures(final JFrame frame, + final Selection selection, + final int maximum_size, + final String max_message) + { + final FeatureVector features_to_check = selection.getAllFeatures(); + + if(features_to_check.size() == 0) + { + new MessageDialog(frame, "No CDS features selected"); + return false; + } + else + { + for(int i = 0 ; i < features_to_check.size() ; ++i) + { + final Feature this_feature = features_to_check.elementAt(i); + if(!this_feature.isCDS()) + { + final String message = + "a non-CDS feature(" + this_feature.getIDString() + + ") is selected - can't continue"; + final MessageDialog dialog = + new MessageDialog(frame, message); + return false; + } + } + + if(features_to_check.size() > maximum_size) + { + final YesNoDialog dialog = + new YesNoDialog(frame, max_message); + + return dialog.getResult(); + } + else + return true; + } + } + + /** + * Check that the are some FeatureSegments in the current selection and + * that the selection isn't too big. If there is more than one + * FeatureSegments in the selection and less than the given maximum then + * return true. If there are no FeatureSegments selected then popup a + * message saying so and return false. If there are more selected + * FeatureSegments than the given maximum than display the message in a + * YesNoDialog component and return the result of the dialog. + **/ + protected boolean checkForSelectionFeatureSegments(final int maximum_size, + final String message) + { + final FeatureSegmentVector segments_to_check = + getSelection().getSelectedSegments(); + + if(segments_to_check.size() == 0) + { + new MessageDialog(getParentFrame(), "No exons selected"); + return false; + } + else + { + if(segments_to_check.size() > maximum_size) + { + final YesNoDialog dialog = + new YesNoDialog(getParentFrame(), message); + + return dialog.getResult(); + } + else + return true; + } + } + + /** + * Check that the current selection contains a MarkerRange. If it does + * then return true. If not then popup a message saying so and return + * false. + **/ + protected boolean checkForSelectionRange() + { + return checkForSelectionRange(getParentFrame(), getSelection()); + } + + /** + * Check that the current selection contains a MarkerRange. If it does + * then return true. If not then popup a message saying so and return + * false. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The selected features to check. + **/ + static boolean checkForSelectionRange(final JFrame frame, + final Selection selection) + { + final MarkerRange marker_range = selection.getMarkerRange(); + + if(marker_range == null) + { + new MessageDialog(frame, "No bases selected"); + return false; + } + else + return true; + } + + /** + * Return the Selection object that was passed to the constructor. + **/ + protected Selection getSelection() + { + return selection; + } + + /** + * + **/ + protected static KeyStroke makeMenuKeyStroke(final int key_code) + { + return KeyStroke.getKeyStroke(key_code, InputEvent.CTRL_MASK); + } + +} + diff --git a/uk/ac/sanger/artemis/components/SelectionViewer.java b/uk/ac/sanger/artemis/components/SelectionViewer.java new file mode 100644 index 000000000..4db4a3ca0 --- /dev/null +++ b/uk/ac/sanger/artemis/components/SelectionViewer.java @@ -0,0 +1,352 @@ +/* SelectionViewer.java + * + * created: Thu Mar 4 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SelectionViewer.java,v 1.1 2004-06-09 09:47:45 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.util.StringVector; +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; + +/** + * This component displays the current selection in a FileViewer component. + * + * @author Kim Rutherford + * @version $Id: SelectionViewer.java,v 1.1 2004-06-09 09:47:45 tjc Exp $ + **/ + +public class SelectionViewer + implements SelectionChangeListener, EntryGroupChangeListener { + /** + * Create a new SelectionViewer object from the given Selection. + **/ + public SelectionViewer (final Selection selection, + final EntryGroup entry_group) { + file_viewer = new FileViewer ("Artemis Selection View"); + + this.selection = selection; + this.entry_group = entry_group; + + readSelection (); + + selection.addSelectionChangeListener (this); + + file_viewer.addWindowListener (new WindowAdapter () { + public void windowClosed (WindowEvent event) { + stopListening (); + } + }); + + entry_group.addEntryGroupChangeListener (this); + } + + /** + * Remove this object as a selection change listener. + **/ + public void stopListening () { + selection.removeSelectionChangeListener (this); + entry_group.removeEntryGroupChangeListener (this); + } + + /** + * Implementation of the SelectionChangeListener interface. We listen to + * SelectionChange events so that we can update the view to reflect the + * current selection. + **/ + public void selectionChanged (SelectionChangeEvent event) { + readSelection (); + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can get rid of the JFrame when the + * EntryGroup is no longer in use (for example when the EntryEdit is + * closed). + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.DONE_GONE: + entry_group.removeEntryGroupChangeListener (this); + file_viewer.dispose (); + break; + } + } + + /** + * Read the selection into this SelectionViewer object. + **/ + public void readSelection () { + final String base_selection_text = + SelectionInfoDisplay.markerRangeText (selection, entry_group); + + final FeatureVector selection_features = selection.getAllFeatures (); + + final StringBuffer buffer = new StringBuffer (); + + final int MAX_FEATURES = 50; + + if (selection_features.size () > MAX_FEATURES) { + buffer.append ("first " + MAX_FEATURES + " features:\n\n"); + } + + for (int i = 0 ; i < selection_features.size () && i < 50 ; ++i) { + buffer.append (selection_features.elementAt (i).toString ()); + } + + if (selection_features.size () > 0) { + buffer.append ("\n"); + } + + if (base_selection_text != null && base_selection_text.length () > 0) { + buffer.append (base_selection_text).append ("\n\n"); + } + + + // this is the maximum number of bases to show + final int display_base_count; + + // this is the maximum number of residues to show + final int display_residues_count; + + if (selection_features.size () == 0) { + display_base_count = 2000; + display_residues_count = 1000; + } else { + display_base_count = 600; + display_residues_count = 300; + } + + + final String selection_bases = selection.getSelectedBases (); + + if (selection_bases != null && selection_bases.length () > 0) { + final StringVector base_summary = getBaseSummary (selection_bases); + + for (int i = 0 ; i < base_summary.size () ; ++i) { + buffer.append (base_summary.elementAt (i)).append ("\n"); + } + } + + if (selection_features.size () == 1) { + double score = + selection_features.elementAt (0).get12CorrelationScore (); + + score = Math.round (score * 100) / 100.0; + + buffer.append ("\nCorrelation score of the selected feature: " + + score + "\n"); + } + + buffer.append ("\n"); + + if (selection_features.size () > 1) { + double correlation_score_total = 0; + + double max_gc_content = -999; + double min_gc_content = 999; + + double max_correlation_score = -999999; + double min_correlation_score = 999999; + + for (int i = 0; i < selection_features.size () ; ++i) { + final Feature this_feature = selection_features.elementAt (i); + + final double correlation_score = this_feature.get12CorrelationScore (); + final double gc_content = this_feature.getPercentGC (); + + correlation_score_total += correlation_score; + + if (min_correlation_score > correlation_score) { + min_correlation_score = correlation_score; + } + + if (max_correlation_score < correlation_score) { + max_correlation_score = correlation_score; + } + + if (min_gc_content > gc_content) { + min_gc_content = gc_content; + } + + if (max_gc_content < gc_content) { + max_gc_content = gc_content; + } + } + + min_gc_content = Math.round (min_gc_content * 100) / 100.0; + max_gc_content = Math.round (max_gc_content * 100) / 100.0; + min_correlation_score = Math.round (min_correlation_score * 100) / 100.0; + max_correlation_score = Math.round (max_correlation_score * 100) / 100.0; + + final double correlation_score_average = + Math.round (correlation_score_total / selection_features.size () * + 100) / 100.0; + + buffer.append ("Average correlation score of the selected features: " + + correlation_score_average + "\n\n"); + + buffer.append ("Min GC percentage of the selected features: " + + min_gc_content + "\n"); + buffer.append ("Max GC percentage of the selected features: " + + max_gc_content + "\n"); + buffer.append ("\n"); + buffer.append ("Min correlation score of the selected features: " + + min_correlation_score + "\n"); + buffer.append ("Max correlation score of the selected features: " + + max_correlation_score + "\n"); + buffer.append ("\n"); + } + + + if (selection_bases.length () > 0 && selection_features.size () <= 1) { + if (selection_bases.length () > display_base_count) { + // display the first display_base_count/2 bases and the last + // display_base_count/2 bases + buffer.append ("first " + display_base_count / 2 + + " bases of selection:\n"); + final String start_sub_string = + selection_bases.substring (0, display_base_count / 2); + buffer.append (start_sub_string).append ("\n\n"); + buffer.append ("last " + display_base_count / 2 + + " bases of selection:\n"); + final String sub_string = + selection_bases.substring (selection_bases.length () - + display_base_count / 2); + buffer.append (sub_string).append ("\n\n"); + } else { + buffer.append ("bases of selection:\n"); + buffer.append (selection_bases).append ("\n\n"); + } + + if (selection_bases.length () >= 3) { + final int residues_to_display; + + if (selection_bases.length () / 3 < display_residues_count) { + residues_to_display = selection_bases.length () / 3; + + buffer.append ("translation of the selected bases:\n"); + } else { + residues_to_display = display_residues_count; + + buffer.append ("translation of the first " + + display_residues_count * 3 + + " selected bases:\n"); + } + + final String residue_bases = + selection_bases.substring (0, residues_to_display * 3); + + final String residues = + AminoAcidSequence.getTranslation (residue_bases, true).toString (); + + buffer.append (residues.toUpperCase ()).append ("\n\n"); + } + } + + file_viewer.setText (buffer.toString ()); + } + + /** + * Return a summary of the base content of the given String. The return + * value is a StringVector containing each line to be output. We return a + * vector so that the caller can do things like indenting the lines. + * @return null if the bases String is empty or null + **/ + public static StringVector getBaseSummary (final String bases) { + if (bases == null || bases.length () == 0) { + return null; + } + + final StringVector return_vector = new StringVector (); + + long counts [] = new long [257]; + + for (int i = 0 ; i < bases.length () ; ++i) { + final char this_char = bases.charAt (i); + + if (this_char > 255) { + // don't know what this is, but it isn't a base + ++counts[256]; + } else { + ++counts[this_char]; + } + } + + for (int i = 0 ; i < 256 ; ++i) { + if (counts[i] > 0) { + return_vector.add (new String (Character.toUpperCase ((char)i) + + " content: " + counts[i] + + " (" + 10000 * counts[i] / + bases.length () / 100.0 + "%)")); + } + } + + final long non_ambiguous_base_count = + counts['g'] + counts['c'] + counts['a'] + counts['t']; + + if (non_ambiguous_base_count == bases.length ()) { + return_vector.add ("(no ambiguous bases)"); + } + + return_vector.add (""); + + return_vector.add (new String ("GC percentage: " + + (Math.round (100.0 * + (counts['g'] + counts['c']) / + bases.length () * 100) / + 100.0))); + + if (non_ambiguous_base_count > 0 && + non_ambiguous_base_count < bases.length ()) { + return_vector.add (new String ("GC percentage of non-ambiguous bases: " + + (Math.round (100.0 * (counts['g'] + + counts['c']) / + non_ambiguous_base_count * + 100) / 100.0))); + } + + return return_vector; + } + + /** + * This is the selection object that we are viewing. + **/ + final private Selection selection; + + /** + * The FileViewer object that is displaying the selection. + **/ + final private FileViewer file_viewer; + + /** + * The EntryGroup that was passed to the constructor. + **/ + final EntryGroup entry_group; +} + + diff --git a/uk/ac/sanger/artemis/components/Selector.java b/uk/ac/sanger/artemis/components/Selector.java new file mode 100644 index 000000000..d9dcd942b --- /dev/null +++ b/uk/ac/sanger/artemis/components/Selector.java @@ -0,0 +1,885 @@ +/* Selector.java + * + * created: Tue Apr 11 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000,2001,2002 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/Selector.java,v 1.1 2004-06-09 09:47:46 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.AminoAcidSequence; + +import uk.ac.sanger.artemis.io.EntryInformation; +import uk.ac.sanger.artemis.util.StringVector; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * This component allows the user to set the selection by filtering the + * features in an EntryGroup on key and contents. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: Selector.java,v 1.1 2004-06-09 09:47:46 tjc Exp $ + **/ + +public class Selector extends JFrame + implements EntryGroupChangeListener { + /** + * Create a new Selector that van set the given Selection. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group The component will choose features from this + * EntryGroup. + * @param goto_event_source The object the we will call gotoBase () on. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public Selector (final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group) { + super ("Artemis Feature Selector"); + + this.selection = selection; + this.entry_group = entry_group; + + final Font default_font = Options.getOptions ().getFont (); + + setFont (default_font); + + GridBagLayout gridbag = new GridBagLayout(); + getContentPane ().setLayout (gridbag); + + GridBagConstraints c = new GridBagConstraints(); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.WEST; + c.weighty = 0; + c.gridwidth = GridBagConstraints.REMAINDER; + + final JLabel top_label = new JLabel ("Select by:"); + gridbag.setConstraints (top_label, c); + getContentPane ().add (top_label); + + + by_key_button = new JCheckBox ("Key: ", false); + final JPanel by_key_panel = new JPanel (); + by_key_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + by_key_panel.add (by_key_button); + c.gridwidth = 2; + gridbag.setConstraints (by_key_panel, c); + getContentPane ().add (by_key_panel); + + final EntryInformation default_entry_information = + Options.getArtemisEntryInformation (); + + key_selector = new KeyChoice (default_entry_information); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (key_selector, c); + getContentPane ().add (key_selector); + + key_selector.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + by_key_button.setSelected (true); + } + }); + + by_key_button.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent e) { + if (!by_key_button.isSelected ()) { + by_qualifier_button.setSelected (false); + } + } + }); + + + by_qualifier_button = new JCheckBox ("Qualifier: ", false); + final JPanel by_qualifier_panel = new JPanel (); + by_qualifier_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + by_qualifier_panel.add (by_qualifier_button); + c.gridwidth = 2; + gridbag.setConstraints (by_qualifier_panel, c); + getContentPane ().add (by_qualifier_panel); + + qualifier_selector = new QualifierChoice (default_entry_information, + key_selector.getSelectedItem (), + null); + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (qualifier_selector, c); + getContentPane ().add (qualifier_selector); + + qualifier_selector.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + by_qualifier_button.setSelected (true); + by_key_button.setSelected (true); + } + }); + + key_selector.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + qualifier_selector.setKey (key_selector.getSelectedItem ()); + } + }); + + by_qualifier_button.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent e) { + if (by_qualifier_button.isSelected ()) { + if (!by_key_button.isSelected ()) { + by_key_button.setSelected (true); + } + } + } + }); + + + final JLabel qualifier_text_label = + new JLabel (" Containing this text: "); + c.gridwidth = 2; + gridbag.setConstraints (qualifier_text_label, c); + getContentPane ().add (qualifier_text_label); + + qualifier_text = new JTextField ("", 18); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (qualifier_text, c); + getContentPane ().add (qualifier_text); + + + ignore_case_checkbox = new JCheckBox ("Ignore Case", true); + + final JPanel ignore_case_panel = new JPanel (); + + ignore_case_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + ignore_case_panel.add (ignore_case_checkbox); + + c.gridwidth = 2; + gridbag.setConstraints (ignore_case_panel, c); + getContentPane ().add (ignore_case_panel); + + + partial_match_checkbox = new JCheckBox ("Allow Partial Match", true); + + final JPanel partial_match_panel = new JPanel (); + + partial_match_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + partial_match_panel.add (partial_match_checkbox); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (partial_match_panel, c); + getContentPane ().add (partial_match_panel); + + + match_any_word_checkbox = new JCheckBox ("Match Any Word", false); + + final JPanel match_any_word_panel = new JPanel (); + + match_any_word_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + match_any_word_panel.add (match_any_word_checkbox); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (match_any_word_panel, c); + getContentPane ().add (match_any_word_panel); + + + { + // leave a blank line to give the user a visual clue + final JLabel blank_label = new JLabel (""); + + gridbag.setConstraints (blank_label, c); + getContentPane ().add (blank_label); + + final JLabel and_label = new JLabel ("And:"); + + gridbag.setConstraints (and_label, c); + getContentPane ().add (and_label); + } + + + less_than_bases_button = new JCheckBox ("Up to: ", false); + final JPanel less_than_bases_panel = new JPanel (); + less_than_bases_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + less_than_bases_panel.add (less_than_bases_button); + c.gridwidth = 3; + gridbag.setConstraints (less_than_bases_panel, c); + getContentPane ().add (less_than_bases_panel); + + less_than_bases_text = new JTextField ("", 18); + + gridbag.setConstraints (less_than_bases_text, c); + getContentPane ().add (less_than_bases_text); + + final JLabel less_than_bases_label = new JLabel (" bases long"); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (less_than_bases_label, c); + getContentPane ().add (less_than_bases_label); + + { + // leave a blank line to give the user a visual clue + final JLabel blank_label = new JLabel (""); + + gridbag.setConstraints (blank_label, c); + getContentPane ().add (blank_label); + + final JLabel and_label = new JLabel ("And:"); + + gridbag.setConstraints (and_label, c); + getContentPane ().add (and_label); + } + + + greater_than_bases_button = new JCheckBox ("At least: ", false); + final JPanel greater_than_bases_panel = new JPanel (); + greater_than_bases_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + greater_than_bases_panel.add (greater_than_bases_button); + c.gridwidth = 2; + gridbag.setConstraints (greater_than_bases_panel, c); + getContentPane ().add (greater_than_bases_panel); + + greater_than_bases_text = new JTextField ("", 18); + + gridbag.setConstraints (greater_than_bases_text, c); + getContentPane ().add (greater_than_bases_text); + + final JLabel greater_than_bases_label = new JLabel (" bases long"); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (greater_than_bases_label, c); + getContentPane ().add (greater_than_bases_label); + + { + // leave a blank line to give the user a visual clue + final JLabel blank_label = new JLabel (""); + + gridbag.setConstraints (blank_label, c); + getContentPane ().add (blank_label); + + final JLabel and_label = new JLabel ("And:"); + + gridbag.setConstraints (and_label, c); + getContentPane ().add (and_label); + } + + less_than_exons_button = new JCheckBox ("Up to: ", false); + final JPanel less_than_exons_panel = new JPanel (); + less_than_exons_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + less_than_exons_panel.add (less_than_exons_button); + c.gridwidth = 3; + gridbag.setConstraints (less_than_exons_panel, c); + getContentPane ().add (less_than_exons_panel); + + less_than_exons_text = new JTextField ("", 18); + + gridbag.setConstraints (less_than_exons_text, c); + getContentPane ().add (less_than_exons_text); + + final JLabel less_than_exons_label = new JLabel (" exons long"); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (less_than_exons_label, c); + getContentPane ().add (less_than_exons_label); + + { + // leave a blank line to give the user a visual clue + final JLabel blank_label = new JLabel (""); + + gridbag.setConstraints (blank_label, c); + getContentPane ().add (blank_label); + + final JLabel and_label = new JLabel ("And:"); + + gridbag.setConstraints (and_label, c); + getContentPane ().add (and_label); + } + + + greater_than_exons_button = new JCheckBox ("At least: ", false); + final JPanel greater_than_exons_panel = new JPanel (); + greater_than_exons_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + greater_than_exons_panel.add (greater_than_exons_button); + c.gridwidth = 2; + gridbag.setConstraints (greater_than_exons_panel, c); + getContentPane ().add (greater_than_exons_panel); + + greater_than_exons_text = new JTextField ("", 18); + + gridbag.setConstraints (greater_than_exons_text, c); + getContentPane ().add (greater_than_exons_text); + + final JLabel greater_than_exons_label = new JLabel (" exons long"); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (greater_than_exons_label, c); + getContentPane ().add (greater_than_exons_label); + + { + // leave a blank line to give the user a visual clue + final JLabel blank_label = new JLabel (""); + + gridbag.setConstraints (blank_label, c); + getContentPane ().add (blank_label); + + final JLabel and_label = new JLabel ("And by:"); + + gridbag.setConstraints (and_label, c); + getContentPane ().add (and_label); + } + + + by_motif_button = new JCheckBox ("Amino acid motif: ", false); + final JPanel by_motif_panel = new JPanel (); + by_motif_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + by_motif_panel.add (by_motif_button); + c.gridwidth = 2; + gridbag.setConstraints (by_motif_panel, c); + getContentPane ().add (by_motif_panel); + + motif_text = new JTextField ("", 18); + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (motif_text, c); + getContentPane ().add (motif_text); + + { + // leave a blank line to give the user a visual clue + final JLabel blank_label = new JLabel (""); + + gridbag.setConstraints (blank_label, c); + getContentPane ().add (blank_label); + } + + final JPanel strand_panel = new JPanel (); + + strand_panel.setLayout (new FlowLayout (FlowLayout.LEFT)); + + forward_strand_checkbox = new JCheckBox ("Forward Strand Features", true); + strand_panel.add (forward_strand_checkbox); + forward_strand_checkbox.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + if (!forward_strand_checkbox.isSelected () && + !reverse_strand_checkbox.isSelected ()) { + // make sure one of the strand is always selected + reverse_strand_checkbox.setSelected (true); + } + } + }); + + + reverse_strand_checkbox = new JCheckBox ("Reverse Strand Features", true); + strand_panel.add (reverse_strand_checkbox); + reverse_strand_checkbox.addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent _) { + if (!reverse_strand_checkbox.isSelected () && + !forward_strand_checkbox.isSelected ()) { + // make sure one of the strand is always selected + forward_strand_checkbox.setSelected (true); + } + } + }); + + + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints (strand_panel, c); + getContentPane ().add (strand_panel); + + + + + + final JButton select_button = new JButton ("Select"); + + select_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + selection.set (getSelected ()); + } + }); + + final JButton view_button = new JButton ("View"); + + view_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + final FeaturePredicate predicate = + new FeatureFromVectorPredicate (getSelected ()); + + String title = "All features"; + + if (by_key_button.isSelected ()) { + title += " with key \"" + key_selector.getSelectedItem () + "\""; + } + + if (by_qualifier_button.isSelected ()) { + if (qualifier_text.getText ().trim ().length () > 0) { + title += + " with qualifier \"" + qualifier_selector.getSelectedItem () + + "\" containing text \"" + qualifier_text.getText () + "\""; + } else { + title += + " with qualifier \"" + + qualifier_selector.getSelectedItem () + "\""; + } + } + + if (forward_strand_checkbox.isSelected () && + !reverse_strand_checkbox.isSelected ()) { + title += " on the forward strand"; + } + + if (!forward_strand_checkbox.isSelected () && + reverse_strand_checkbox.isSelected ()) { + title += " on the reverse strand"; + } + + if (by_motif_button.isSelected ()) { + title += + " with motif: " + motif_text.getText ().trim ().toUpperCase (); + } + + if (less_than_bases_button.isSelected ()) { + title += + " at most " + less_than_bases_text.getText ().trim () + + " bases long"; + } + + if (greater_than_bases_button.isSelected ()) { + title += + " at least " + greater_than_bases_text.getText ().trim () + + " bases long"; + } + + if (less_than_exons_button.isSelected ()) { + title += + " at most " + less_than_exons_text.getText ().trim () + + " exons long"; + } + + if (greater_than_exons_button.isSelected ()) { + title += + " at least " + greater_than_exons_text.getText ().trim () + + " exons long"; + } + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, predicate, title); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (title, + getSelection (), + goto_event_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + }); + + + final JButton close_button = new JButton ("Close"); + + close_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + Selector.this.dispose (); + } + }); + + final FlowLayout flow_layout = + new FlowLayout (FlowLayout.CENTER, 15, 5); + + final JPanel bottom_button_panel = new JPanel (flow_layout); + + bottom_button_panel.add (select_button); + bottom_button_panel.add (view_button); + bottom_button_panel.add (close_button); + + gridbag.setConstraints (bottom_button_panel, c); + getContentPane ().add (bottom_button_panel); + + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent event) { + getEntryGroup ().removeEntryGroupChangeListener (Selector.this); + Selector.this.dispose (); + } + }); + + getEntryGroup ().addEntryGroupChangeListener (this); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + + setVisible (true); + } + + /** + * Implementation of the EntryGroupChangeListener interface. We listen to + * EntryGroupChange events so that we can get rid of the Navigator when the + * EntryGroup is no longer in use (for example when the EntryEdit is + * closed). + **/ + public void entryGroupChanged (final EntryGroupChangeEvent event) { + switch (event.getType ()) { + case EntryGroupChangeEvent.DONE_GONE: + getEntryGroup ().removeEntryGroupChangeListener (this); + dispose (); + break; + } + } + + /** + * Return those features that match the current setting of the Selector. + **/ + private FeatureVector getSelected () { + final FeaturePredicate key_and_qualifier_predicate; + final FeaturePredicate motif_predicate; + + if (by_key_button.isSelected ()) { + if (by_qualifier_button.isSelected ()) { + final String search_text = qualifier_text.getText ().trim (); + final String qualifier_name = + (String) qualifier_selector.getSelectedItem (); + + if (search_text.length () == 0) { + key_and_qualifier_predicate = + new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (), + qualifier_name, + true); + } else { + if (match_any_word_checkbox.isSelected ()) { + final FeaturePredicateVector temp_predicates = + new FeaturePredicateVector (); + + final StringVector words = + StringVector.getStrings (search_text, " \t\n"); + + for (int i = 0 ; i < words.size () ; ++i) { + final String this_word = words.elementAt (i); + final FeaturePredicate new_predicate = + new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (), + qualifier_name, + this_word, + partial_match_checkbox.isSelected (), + ignore_case_checkbox.isSelected ()); + + temp_predicates.add (new_predicate); + } + + key_and_qualifier_predicate = + new FeaturePredicateConjunction (temp_predicates, + FeaturePredicateConjunction.OR); + } else { + key_and_qualifier_predicate = + new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (), + qualifier_name, + search_text, + partial_match_checkbox.isSelected (), + ignore_case_checkbox.isSelected ()); + + } + } + } else { + final String search_text = qualifier_text.getText ().trim (); + if (search_text.length () == 0) { + key_and_qualifier_predicate = + new FeatureKeyPredicate (key_selector.getSelectedItem ()); + } else { + if (match_any_word_checkbox.isSelected ()) { + final FeaturePredicateVector temp_predicates = + new FeaturePredicateVector (); + + final StringVector words = + StringVector.getStrings (search_text, " \t\n"); + + + for (int i = 0 ; i < words.size () ; ++i) { + final String this_word = words.elementAt (i); + final FeaturePredicate new_predicate = + new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (), + null, // match any qialifier + this_word, + partial_match_checkbox.isSelected (), + ignore_case_checkbox.isSelected ()); + temp_predicates.add (new_predicate); + } + + key_and_qualifier_predicate = + new FeaturePredicateConjunction (temp_predicates, + FeaturePredicateConjunction.OR); + } else { + key_and_qualifier_predicate = + new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (), + null, // match any qialifier + search_text, + partial_match_checkbox.isSelected (), + ignore_case_checkbox.isSelected ()); + } + } + } + } else { + key_and_qualifier_predicate = null; + } + + if (by_motif_button.isSelected ()) { + final AminoAcidSequence amino_acid_sequence = + new AminoAcidSequence (motif_text.getText ().trim ()); + + motif_predicate = + new FeaturePatternPredicate (amino_acid_sequence); + } else { + motif_predicate = null; + } + + FeaturePredicate less_than_bases_predicate = null; + + if (less_than_bases_button.isSelected () && + less_than_bases_text.getText ().trim ().length () > 0) { + try { + final int less_than_bases_int = + Integer.valueOf (less_than_bases_text.getText ().trim ()).intValue (); + + less_than_bases_predicate = new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (feature.getBaseCount () <= less_than_bases_int) { + return true; + } else { + return false; + } + } + }; + } catch (NumberFormatException e) { + new MessageDialog (this, + "warning this is not a number: " + + less_than_bases_text.getText ()); + less_than_bases_predicate = null; + } + } else { + less_than_bases_predicate = null; + } + + FeaturePredicate greater_than_bases_predicate = null; + + if (greater_than_bases_button.isSelected () && + greater_than_bases_text.getText ().trim ().length () > 0) { + try { + final int greater_than_bases_int = + Integer.valueOf (greater_than_bases_text.getText ().trim ()).intValue (); + + greater_than_bases_predicate = new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (feature.getBaseCount () >= greater_than_bases_int) { + return true; + } else { + return false; + } + } + }; + } catch (NumberFormatException e) { + new MessageDialog (this, + "warning this is not a number: " + + greater_than_bases_text.getText ()); + greater_than_bases_predicate = null; + } + } else { + greater_than_bases_predicate = null; + } + + FeaturePredicate less_than_exons_predicate = null; + + if (less_than_exons_button.isSelected () && + less_than_exons_text.getText ().trim ().length () > 0) { + try { + final int less_than_exons_int = + Integer.valueOf (less_than_exons_text.getText ().trim ()).intValue (); + + less_than_exons_predicate = new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (feature.getSegments ().size () <= less_than_exons_int) { + return true; + } else { + return false; + } + } + }; + } catch (NumberFormatException e) { + new MessageDialog (this, + "warning this is not a number: " + + less_than_exons_text.getText ()); + less_than_exons_predicate = null; + } + } else { + less_than_exons_predicate = null; + } + + FeaturePredicate greater_than_exons_predicate = null; + + if (greater_than_exons_button.isSelected () && + greater_than_exons_text.getText ().trim ().length () > 0) { + try { + final int greater_than_exons_int = + Integer.valueOf (greater_than_exons_text.getText ().trim ()).intValue (); + + greater_than_exons_predicate = new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (feature.getSegments ().size () >= greater_than_exons_int) { + return true; + } else { + return false; + } + } + }; + } catch (NumberFormatException e) { + new MessageDialog (this, + "warning this is not a number: " + + greater_than_exons_text.getText ()); + greater_than_exons_predicate = null; + } + } else { + greater_than_exons_predicate = null; + } + + FeaturePredicate predicate = null; + + if (!by_key_button.isSelected () && !by_motif_button.isSelected ()) { + // default to selecting all features + predicate = new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + return true; + } + }; + } else { + if (motif_predicate != null && key_and_qualifier_predicate != null) { + predicate = + new FeaturePredicateConjunction (key_and_qualifier_predicate, + motif_predicate, + FeaturePredicateConjunction.AND); + } else { + if (motif_predicate != null) { + predicate = motif_predicate; + } else { + predicate = key_and_qualifier_predicate; + } + } + } + + if (less_than_bases_predicate != null) { + predicate = + new FeaturePredicateConjunction (predicate, + less_than_bases_predicate, + FeaturePredicateConjunction.AND); + } + + if (greater_than_bases_predicate != null) { + predicate = + new FeaturePredicateConjunction (predicate, + greater_than_bases_predicate, + FeaturePredicateConjunction.AND); + } + + if (less_than_exons_predicate != null) { + predicate = + new FeaturePredicateConjunction (predicate, + less_than_exons_predicate, + FeaturePredicateConjunction.AND); + } + + if (greater_than_exons_predicate != null) { + predicate = + new FeaturePredicateConjunction (predicate, + greater_than_exons_predicate, + FeaturePredicateConjunction.AND); + } + + final FeatureEnumeration test_enumerator = entry_group.features (); + + final FeatureVector return_features = new FeatureVector (); + + while (test_enumerator.hasMoreFeatures ()) { + final Feature this_feature = test_enumerator.nextFeature (); + + if (predicate.testPredicate (this_feature)) { + if (this_feature.isForwardFeature ()) { + if (forward_strand_checkbox.isSelected ()) { + return_features.add (this_feature); + } + } else { + if (reverse_strand_checkbox.isSelected ()) { + return_features.add (this_feature); + } + } + } + } + + return return_features; + } + + /** + * Return the Selection object that was passed to the constructor. + **/ + private Selection getSelection () { + return selection; + } + + /** + * Return the EntryGroup object that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + private JCheckBox by_key_button; + private JCheckBox by_qualifier_button; + private JCheckBox by_motif_button; + private JCheckBox less_than_bases_button; + private JCheckBox greater_than_bases_button; + private JCheckBox less_than_exons_button; + private JCheckBox greater_than_exons_button; + private JCheckBox ignore_case_checkbox; + private JCheckBox match_any_word_checkbox; + private JCheckBox forward_strand_checkbox; + private JCheckBox reverse_strand_checkbox; + private KeyChoice key_selector; + private QualifierChoice qualifier_selector; + private JTextField qualifier_text; + private JTextField motif_text; + private JTextField less_than_bases_text; + private JTextField greater_than_bases_text; + private JTextField less_than_exons_text; + private JTextField greater_than_exons_text; + + /** + * If checked the search text is allowed to match a substring of a + * qualifier value. + **/ + final JCheckBox partial_match_checkbox; + + /** + * The EntryGroup object that was passed to the constructor. + **/ + final EntryGroup entry_group; + + /** + * This is the Selection that was passed to the constructor. + **/ + final private Selection selection; +} diff --git a/uk/ac/sanger/artemis/components/SequenceViewer.java b/uk/ac/sanger/artemis/components/SequenceViewer.java new file mode 100644 index 000000000..7015f7e98 --- /dev/null +++ b/uk/ac/sanger/artemis/components/SequenceViewer.java @@ -0,0 +1,165 @@ +/* SequenceViewer.java + * + * created: Sat Dec 19 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SequenceViewer.java,v 1.1 2004-06-09 09:47:47 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import java.io.StringWriter; +import java.io.PrintWriter; + +/** + * This component provides a viewer for dna or amino acid sequences. The + * units are numbered automatically, given a view like this: + * <pre> + * ATGATGATGATGATATGCATGATCG + * 10 20 + * </pre> + * @author Kim Rutherford + * @version $Id: SequenceViewer.java,v 1.1 2004-06-09 09:47:47 tjc Exp $ + **/ + +public class SequenceViewer extends FileViewer { + /** + * Create a new SequenceViewer component. + * @param title The name to attach to the new JFrame. + * @param include_numbers If true then the sequence will be numbered + * (every second line of the display will be numbers rather than + * sequence). + **/ + public SequenceViewer (final String title, + final boolean include_numbers) { + super (title); + + this.include_numbers = include_numbers; + } + + /** + * Set the sequence to display. + * @param comment_line A comment to put on the first line. This may be + * null in which case there is no comment line. This String should not + * be "\n" terminated. + * @param sequence The sequence to display. This may be null in which case + * there is no comment line. This String should not be "\n" terminated. + **/ + public void setSequence (final String comment_line, final String sequence) { + this.comment_line = comment_line; + this.sequence = sequence; + + setView (); + } + + /** + * Set the sequence to display with no comment line. + * @param sequence The sequence to display. This may be null in which case + * there is no comment line. This String should not be "\n" terminated. + **/ + public void setSequence (final String sequence) { + this.comment_line = null; + this.sequence = sequence; + + setView (); + } + + /** + * Write the sequence and comment_line in the view. + **/ + private void setView () { + final StringWriter string_writer = new StringWriter (); + + final PrintWriter writer = new PrintWriter (string_writer); + + if (comment_line != null) { + writer.println (comment_line); + } + + String rest_of_sequence = sequence; + + final int UNITS_PER_LINE = 60; + + int line_count = 0; + + while (rest_of_sequence != null) { + final String this_line; + + if (rest_of_sequence.length () < UNITS_PER_LINE) { + this_line = rest_of_sequence; + rest_of_sequence = null; + } else { + this_line = rest_of_sequence.substring (0, UNITS_PER_LINE); + rest_of_sequence = rest_of_sequence.substring (UNITS_PER_LINE); + } + + + writer.println (this_line); + + if (include_numbers) { + final int COUNT_INTERVAL = 10; + + for (int i = 1 ; + i <= this_line.length () / COUNT_INTERVAL; + ++i) { + final int this_unit_count = + i * COUNT_INTERVAL + line_count * UNITS_PER_LINE; + + final int number_of_spaces = + COUNT_INTERVAL - String.valueOf (this_unit_count).length (); + + for (int space_index = 0 ; + space_index < number_of_spaces ; + ++space_index) { + writer.print (' '); + } + + writer.print (this_unit_count); + } + + writer.println (); + } + + ++line_count; + } + + writer.flush (); + + setText (string_writer.toString ()); + } + + /** + * The sequence to display. + **/ + private String sequence = ""; + + /** + * A comment to put on the first line. If null don't display any comment. + **/ + private String comment_line = null; + + /** + * If true then the amino acids will be numbered (every second line of the + * display will be numbers rather than sequence). + **/ + private final boolean include_numbers; +} + + diff --git a/uk/ac/sanger/artemis/components/Splash.java b/uk/ac/sanger/artemis/components/Splash.java new file mode 100644 index 000000000..7e67f4194 --- /dev/null +++ b/uk/ac/sanger/artemis/components/Splash.java @@ -0,0 +1,526 @@ +/* Splash.java + * + * created: Wed May 10 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000,2002 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/Splash.java,v 1.1 2004-06-09 09:47:48 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; +import uk.ac.sanger.artemis.EntrySourceVector; +import uk.ac.sanger.artemis.Logger; +import uk.ac.sanger.artemis.util.InputStreamProgressListener; +import uk.ac.sanger.artemis.util.InputStreamProgressEvent; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.border.Border; + +/** + * Base class that creates a generic "Splash Screen" + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: Splash.java,v 1.1 2004-06-09 09:47:48 tjc Exp $ + **/ + +abstract public class Splash extends JFrame +{ + + /** + * Do any necessary cleanup then exit. + **/ + abstract protected void exit(); + + /** + * A label for status and error messages. + **/ + final private JLabel status_line = new JLabel(""); + + /** + * The program name that was passed to the constructor. + **/ + private String program_name; + + /** + * The program version that was passed to the constructor. + **/ + private String program_version; + + /** + * The JComponent to draw the main splash screen into + **/ + private JComponent helix_canvas; + + /** + * JMenu bar for the main window. + **/ + private JMenuBar menu_bar; + + protected JMenu file_menu; + + protected JMenu options_menu; + + /** + * Create a new JFrame for a Splash screen. + * @param program_name The full name of the program. + * @param program_title The name to use in the title. + * @param program_version The version string. + **/ + public Splash(final String program_name, + final String program_title, + final String program_version) + { + super(program_title + " " + program_version); + + this.program_name = program_name; + this.program_version = program_version; + + addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent event) + { + exit(); + } + }); + + final javax.swing.LookAndFeel look_and_feel = + javax.swing.UIManager.getLookAndFeel(); + + final javax.swing.plaf.FontUIResource font_ui_resource = + Options.getOptions().getFontUIResource(); + + java.util.Enumeration keys = UIManager.getDefaults().keys(); + while(keys.hasMoreElements()) + { + Object key = keys.nextElement(); + Object value = UIManager.get(key); + if(value instanceof javax.swing.plaf.FontUIResource) + UIManager.put(key, font_ui_resource); + } + + getContentPane().setLayout(new BorderLayout()); + + makeAllMenus(); + + helix_canvas = makeHelixCanvas(); + + status_line.setFont(Options.getOptions().getFont()); + + final FontMetrics fm = + this.getFontMetrics(status_line.getFont()); + + final int font_height = fm.getHeight()+10; + + status_line.setMinimumSize(new Dimension(100, font_height)); + status_line.setPreferredSize(new Dimension(100, font_height)); + + Border loweredbevel = BorderFactory.createLoweredBevelBorder(); + Border raisedbevel = BorderFactory.createRaisedBevelBorder(); + Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel); + status_line.setBorder(compound); + + getContentPane().add(helix_canvas, "Center"); + getContentPane().add(status_line, "South"); + + ClassLoader cl = this.getClass().getClassLoader(); + ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif")); + + if(icon != null) + { + final Image icon_image = icon.getImage(); + MediaTracker tracker = new MediaTracker(this); + tracker.addImage(icon_image, 0); + + try + { + tracker.waitForAll(); + setIconImage(icon_image); + } + catch(InterruptedException e) + { + // ignore and continue + } + } + + pack(); + + final int x = 460; + final int y = 250; + + setSize(x, y); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation(new Point((screen.width - getSize().width) / 2, + (screen.height - getSize().height) / 2)); + } + + /** + * Return a JComponent object that will display a helix and a short + * copyright notice. + **/ + private JComponent makeHelixCanvas() + { + return new JPanel() { + public void update(Graphics g) + { + paint(g); + } + + // return the program name and the program mode in one String + private String getNameString() + { + if(Options.getOptions().isEukaryoticMode()) + return program_name + " [Eukaryotic mode]"; + else + return program_name + " [Prokaryotic mode]"; + } + + /** + * Draws the splash screen text. + **/ + public int textPaint(final Graphics g) + { + FontMetrics fm = this.getFontMetrics(g.getFont()); + final int font_height = fm.getHeight() + 3; + g.setColor(Color.black); + final int left_margin = 5; + + g.drawString(program_name, + helix_width + left_margin, font_height); + g.drawString(program_version, + helix_width + left_margin, font_height * 2); + if(Options.getOptions().isEukaryoticMode()) + g.drawString("[Eukaryotic mode]", + helix_width + left_margin, font_height * 3); + else + g.drawString("[Prokaryotic mode]", + helix_width + left_margin, font_height * 3); + g.drawString("Copyright 1998 - 2003", + helix_width + left_margin, font_height * 9 / 2); + g.drawString("Genome Research Limited", + helix_width + left_margin, font_height * 11 / 2); + + return font_height; + } + + public void paint(final Graphics g) + { + final boolean simple_splash_screen = + Options.getOptions().getPropertyTruthValue("simple_splash_screen"); + + g.setColor(Color.white); + + g.fillRect(0, 0, this.getSize().width, this.getSize().height); + + if(simple_splash_screen) { + // java SIGILL bug work-around + textPaint(g); + return; + } + + if(helix == null) + { +// Toolkit toolkit = Toolkit.getDefaultToolkit(); +// final URL helix_url = Splash.class.getResource("/uk.ac.sanger.artemis/helix.gif"); +// helix = toolkit.getImage(helix_url); + + ClassLoader cl = this.getClass().getClassLoader(); + ImageIcon helix_icon = new ImageIcon(cl.getResource("images/helix.gif")); + helix = helix_icon.getImage(); + +// final URL sanger_url = +// Splash.class.getResource("/uk.ac.sanger.artemis/sanger-centre.gif"); +// sanger = toolkit.getImage(sanger_url); + ImageIcon sanger_icon = new ImageIcon(cl.getResource("images/sanger-centre.gif")); + sanger = sanger_icon.getImage(); + + tracker = new MediaTracker(this); + tracker.addImage(helix, 0); + tracker.addImage(sanger, 1); + + try + { + tracker.waitForAll(); + helix_height = helix.getHeight(this); + helix_width = helix.getWidth(this); + sanger_height = sanger.getHeight(this); + } + catch(InterruptedException e) + { + return; + } + } + + for(int i=0; i*helix_height<=this.getSize().height; ++i) + g.drawImage(helix, + 0, + i * helix_height, this); + + final int font_height = textPaint(g); + + int sanger_position = this.getSize().height - sanger_height; + + if(sanger_position > font_height * 5.5) + g.drawImage(sanger, + helix_width + 5, + sanger_position, this); + } + + MediaTracker tracker = null; + + /** + * The image of the Sanger DNA logo. This is set in paint(). + **/ + private Image helix = null; + + /** + * The image of the Sanger logo. This is set in paint(). + **/ + private Image sanger = null; + + /** + * The height of the Sanger logo. This is set in paint(). + **/ + private int sanger_height; + + /** + * The height of the Sanger DNA logo. This is set in paint(). + **/ + private int helix_height; + + /** + * The width of the Sanger DNA logo. This is set in paint(). + **/ + private int helix_width; + }; + } + + /** + * Return the reference of the Label used as a status line. + **/ + public JLabel getStatusLabel() + { + return status_line; + } + + /** + * The possible sources for reading Entry objects. + **/ + public EntrySourceVector getEntrySources(final JFrame frame) + { + return Utilities.getEntrySources(frame, stream_progress_listener); + } + + /** + * Return an InputStreamProgressListener which updates the error label with + * the current number of chars read while reading + **/ + public InputStreamProgressListener getInputStreamProgressListener() + { + return stream_progress_listener; + } + + /** + * Force the options files to be re-read and the EntryEdit components to be + * redisplayed. + **/ + private void resetOptions() + { + Options.getOptions().reset(); + } + + /** + * Make all the menus and menu items for the main window. Also sets up + * suitable ActionListener objects for each item. + */ + private void makeAllMenus() + { + menu_bar = new JMenuBar(); + file_menu = new JMenu("File"); + options_menu = new JMenu("Options"); + + menu_bar.add(file_menu); + menu_bar.add(options_menu); + + setJMenuBar(menu_bar); + + ActionListener menu_listener = null; + + menu_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + resetOptions(); + } + }; + makeMenuItem(options_menu, "Re-read Options", menu_listener); + + final JCheckBoxMenuItem enable_direct_edit_item = + new JCheckBoxMenuItem("Enable Direct Editing"); + enable_direct_edit_item.setState(Options.getOptions().canDirectEdit()); + enable_direct_edit_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + final boolean item_state = enable_direct_edit_item.getState(); + Options.getOptions().setDirectEdit(item_state); + } + }); + options_menu.add(enable_direct_edit_item); + + final JCheckBoxMenuItem enable_euk_mode_item = new JCheckBoxMenuItem( + "Eukaryotic Mode"); + enable_euk_mode_item.setState(Options.getOptions().isEukaryoticMode()); + enable_euk_mode_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + final boolean item_state = enable_euk_mode_item.getState(); + Options.getOptions().setEukaryoticMode(item_state); + + helix_canvas.repaint(); + } + }); + options_menu.add(enable_euk_mode_item); + + final JCheckBoxMenuItem highlight_active_entry_item = + new JCheckBoxMenuItem("Highlight Active Entry"); + final boolean highlight_active_entry_state = + Options.getOptions().highlightActiveEntryFlag(); + highlight_active_entry_item.setState(highlight_active_entry_state); + highlight_active_entry_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + final boolean item_state = highlight_active_entry_item.getState(); + Options.getOptions().setHighlightActiveEntryFlag(item_state); + } + }); + options_menu.add(highlight_active_entry_item); + + if(Options.getOptions().getPropertyTruthValue("sanger_options") && + Options.getOptions().getProperty("black_belt_mode") != null) + { + final JCheckBoxMenuItem black_belt_mode_item = + new JCheckBoxMenuItem("Black Belt Mode"); + final boolean state = + Options.getOptions().isBlackBeltMode(); + black_belt_mode_item.setState(state); + black_belt_mode_item.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + final boolean item_state = black_belt_mode_item.getState(); + if(item_state) + Options.getOptions().put("black_belt_mode", "true"); + else + Options.getOptions().put("black_belt_mode", "false"); + } + }); + options_menu.add(black_belt_mode_item); + } + + if(Options.isUnixHost()) + { + options_menu.addSeparator(); + + menu_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + showLog(); + } + }; + makeMenuItem(options_menu, "Show Log Window", menu_listener); + + menu_listener = new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + logger.setVisible(false); + } + }; + makeMenuItem(options_menu, "Hide Log Window", menu_listener); + } + } + + /** + * Make a new menu item in the given menu, with its label given the + * String and add the given ActionListener to it. + */ + protected static void makeMenuItem(JMenu menu, String name, + ActionListener listener) + { + JMenuItem new_item = new JMenuItem(name); + menu.add(new_item); + new_item.addActionListener(listener); + if(name.equals("Open ...")) + new_item.setAccelerator(KeyStroke.getKeyStroke + (KeyEvent.VK_O, InputEvent.CTRL_MASK)); + } + + /** + * Return a Logger for warnings/errors/messages. + **/ + public static Logger getLogger() + { + return logger; + } + + /** + * Return the JComponent that the Splash screen is drawing on. + **/ + public JComponent getCanvas() + { + return helix_canvas; + } + + /** + * Make the LogViewer visible. + **/ + public static void showLog() + { + logger.setVisible(true); + } + + /** + * The Logger that is returned by getLogger(). + **/ + private final static LogViewer logger = new LogViewer(); + + /** + * An InputStreamProgressListener used to update the error label with the + * current number of chars read. + **/ + private final InputStreamProgressListener stream_progress_listener = + new InputStreamProgressListener() { + public void progressMade(final InputStreamProgressEvent event) + { + final int char_count = event.getCharCount(); + if(char_count == -1) + getStatusLabel().setText(""); + else + getStatusLabel().setText("chars read so far: " + char_count); + } + }; +} diff --git a/uk/ac/sanger/artemis/components/StickyFileChooser.java b/uk/ac/sanger/artemis/components/StickyFileChooser.java new file mode 100644 index 000000000..ed43f18a7 --- /dev/null +++ b/uk/ac/sanger/artemis/components/StickyFileChooser.java @@ -0,0 +1,99 @@ +/* StickyFileChooser.java + * + * created: Mon Sep 1 2003 + * + * This file is part of Artemis + * + * Copyright (C) 2003 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/StickyFileChooser.java,v 1.1 2004-06-09 09:47:49 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; + +import java.io.File; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.filechooser.FileSystemView; + +/** + * A JFileChooser that remembers which directory it is in for next time. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: StickyFileChooser.java,v 1.1 2004-06-09 09:47:49 tjc Exp $ + **/ + +public class StickyFileChooser extends JFileChooser +{ + + /** + * Used to remember the directory the JFileChooser was in when the user + * pressed OK. This is used as the starting directory next time. + **/ + private static File last_directory = null; + + /** + * Create a new StickyFileChooser and set the current directory to what is + * was after the last call to StickyFileChooser.showOpenDialog() or + * StickyFileChooser.showSaveDialog(). + **/ + public StickyFileChooser() + { + super(); + + if (last_directory == null) + { + if (Options.getOptions ().getProperty ("default_directory") != null) + { + final String default_directory = + Options.getOptions().getProperty("default_directory"); + setCurrentDirectory (new File (default_directory)); + } + else + { + if (last_directory == null) + last_directory = new File (System.getProperty ("user.dir")); + } + } + + setCurrentDirectory(last_directory); + setSize(620, 460); + } + + /** + * Calls super.showOpenDialog() then remembers the current directory. + **/ + public int showOpenDialog(JFrame owner) + { + int status = super.showOpenDialog(owner); + last_directory = getCurrentDirectory (); + return status; + } + + /** + * Calls super.showSaveDialog() then remembers the current directory. + **/ + public int showSaveDialog(JFrame owner) + { + int status = super.showSaveDialog (owner); + last_directory = getCurrentDirectory (); + return status; + } + +} diff --git a/uk/ac/sanger/artemis/components/SwingWorker.java b/uk/ac/sanger/artemis/components/SwingWorker.java new file mode 100644 index 000000000..6d03d4dcb --- /dev/null +++ b/uk/ac/sanger/artemis/components/SwingWorker.java @@ -0,0 +1,150 @@ +/******************************************************************** +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library 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 +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +********************************************************************/ + +package uk.ac.sanger.artemis.components; + +import javax.swing.SwingUtilities; + +/** +* This is the 3rd version of SwingWorker (also known as +* SwingWorker 3), an abstract class that you subclass to +* perform GUI-related work in a dedicated thread. For +* instructions on using this class, see: +* +* http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html +* +* Note that the API changed slightly in the 3rd version: +* You must now invoke start() on the SwingWorker after +* creating it. +*/ +public abstract class SwingWorker { + private Object value; // see getValue(), setValue() + private Thread thread; + + /** + * Class to maintain reference to current worker thread + * under separate synchronization control. + */ + private static class ThreadVar { + private Thread thread; + ThreadVar(Thread t) { thread = t; } + synchronized Thread get() { return thread; } + synchronized void clear() { thread = null; } + } + + private ThreadVar threadVar; + + /** + * Get the value produced by the worker thread, or null if it + * hasn't been constructed yet. + */ + protected synchronized Object getValue() { + return value; + } + + /** + * Set the value produced by worker thread + */ + private synchronized void setValue(Object x) { + value = x; + } + + /** + * Compute the value to be returned by the <code>get</code> method. + */ + public abstract Object construct(); + + /** + * Called on the event dispatching thread (not on the worker thread) + * after the <code>construct</code> method has returned. + */ + public void finished() { + } + + /** + * A new method that interrupts the worker thread. Call this method + * to force the worker to stop what it's doing. + */ + public void interrupt() { + Thread t = threadVar.get(); + if (t != null) { + t.interrupt(); + } + threadVar.clear(); + } + + /** + * Return the value created by the <code>construct</code> method. + * Returns null if either the constructing thread or the current + * thread was interrupted before a value was produced. + * + * @return the value created by the <code>construct</code> method + */ + public Object get() { + while (true) { + Thread t = threadVar.get(); + if (t == null) { + return getValue(); + } + try { + t.join(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); // propagate + return null; + } + } + } + + + /** + * Start a thread that will call the <code>construct</code> method + * and then exit. + */ + public SwingWorker() { + final Runnable doFinished = new Runnable() { + public void run() { finished(); } + }; + + Runnable doConstruct = new Runnable() { + public void run() { + try { + setValue(construct()); + } + finally { + threadVar.clear(); + } + + SwingUtilities.invokeLater(doFinished); + } + }; + + Thread t = new Thread(doConstruct); + threadVar = new ThreadVar(t); + } + + /** + * Start the worker thread. + */ + public void start() { + Thread t = threadVar.get(); + if (t != null) { + t.start(); + } + } +} diff --git a/uk/ac/sanger/artemis/components/TaskViewerFrame.java b/uk/ac/sanger/artemis/components/TaskViewerFrame.java new file mode 100644 index 000000000..ecf04bce2 --- /dev/null +++ b/uk/ac/sanger/artemis/components/TaskViewerFrame.java @@ -0,0 +1,88 @@ +/* TaskViewerFrame.java + * + * created: Mon Aug 18 2003 + * + * This file is part of Artemis + * + * Copyright(C) 2003 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/TaskViewerFrame.java,v 1.1 2004-06-09 09:47:51 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.jcon.gui.TaskViewer; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** + * TaskViewerFrame class + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: TaskViewerFrame.java,v 1.1 2004-06-09 09:47:51 tjc Exp $ + **/ + +public class TaskViewerFrame extends JFrame +{ + + /** TaskViewer that was created in the constructor. */ + private TaskViewer tv = null; + + + /** + * Create a new TaskViewerFrame to monitor the tasks with the given ids. + **/ + public TaskViewerFrame(final int [] ids) + { + try + { + tv = new TaskViewer(ids); + } + catch(Exception e) + { + dispose(); + new MessageDialog(this, "Exception when viewing tasks: " + + e.getMessage()); + return; + } + + final JPanel panel = new JPanel(); + getContentPane().add(panel); + panel.setLayout(new BorderLayout()); + panel.add(tv, "Center"); + + final JButton close_button = new JButton("Close"); + close_button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + dispose(); + } + }); + + panel.add(close_button, "South"); + pack(); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + setLocation(new Point((screen.width - getSize().width) / 2, + (screen.height - getSize().height) / 2)); + } + +} diff --git a/uk/ac/sanger/artemis/components/TextDialog.java b/uk/ac/sanger/artemis/components/TextDialog.java new file mode 100644 index 000000000..f263cb425 --- /dev/null +++ b/uk/ac/sanger/artemis/components/TextDialog.java @@ -0,0 +1,119 @@ +/* TextDialog.java + * + * created: Mon Jan 11 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextDialog.java,v 1.1 2004-06-09 09:47:52 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * A popup TextField JDialog with an OK and a Cancel button. + * + * @author Kim Rutherford + * @version $Id: TextDialog.java,v 1.1 2004-06-09 09:47:52 tjc Exp $ + **/ + +public class TextDialog extends JDialog { + /** + * Create a new TextDialog component with the given prompt. Other + * components can listen for TextDialogEvent object. + * @param parent The parent window. + * @param prompt A message that is displayed in the component beside the + * TextArea that the user types into. This String is also used as the + * JFrame title. + * @param width The width of the new TextField. + * @param initial_text The initial text to put in the TextField. + **/ + public TextDialog (final JFrame parent, + final String prompt, + final int width, + final String initial_text) { + super (parent, prompt, true); + + getContentPane ().add (new JLabel (prompt), "North"); + + final JPanel panel = new JPanel (); + + panel.add (ok_button); + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + text = text_field.getText (); + TextDialog.this.dispose (); + } + }); + + panel.add (cancel_button); + cancel_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + text = null; + TextDialog.this.dispose (); + } + }); + + getContentPane ().add (panel, "South"); + + text_field = new JTextField (initial_text, width); + + text_field.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + if (e.getKeyChar () == '\n') { + text = text_field.getText (); + TextDialog.this.dispose (); + } + } + }); + + getContentPane ().add (text_field, "Center"); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + } + + /** + * Wait for a user action then return the text if the user hit OK or null + * if the user hit Cancel. + **/ + public String getText () { + setVisible (true); + + return text; + } + + private final JButton ok_button = new JButton ("OK"); + private final JButton cancel_button = new JButton ("Cancel"); + private JTextField text_field = null; + + /** + * Set to null if and only if the user cancels the dialog, otherwise + * contains the text that the user entered. + **/ + private String text = null; +} diff --git a/uk/ac/sanger/artemis/components/TextFieldSink.java b/uk/ac/sanger/artemis/components/TextFieldSink.java new file mode 100644 index 000000000..61ba9d4b8 --- /dev/null +++ b/uk/ac/sanger/artemis/components/TextFieldSink.java @@ -0,0 +1,142 @@ +/******************************************************************** +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library 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 +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +* +* Copyright (C) Genome Research Limited +* +********************************************************************/ + +package uk.ac.sanger.artemis.components; + +import java.awt.datatransfer.*; +import javax.swing.*; +import javax.swing.event.*; +import java.awt.event.*; +import javax.swing.border.*; +import java.awt.dnd.*; +import java.awt.*; +import java.io.*; + + +/** +* +* Extends JTextField to enable pasting & drag and drop into it. +* +*/ +public class TextFieldSink extends JTextField implements DropTargetListener +{ + + public TextFieldSink(String text, int columns) + { + super(text, columns); + //pasting + addMouseListener(new MouseAdapter() + { + public void mouseClicked(MouseEvent e) + { + if(e.getClickCount() == 2) + { + pasteText(); + e.consume(); + }; + } + }); + + setDropTarget(new DropTarget(this,this)); + } + + public TextFieldSink() + { + this("",0); + } + + public void pasteText() + { + Clipboard c = this.getToolkit().getSystemClipboard(); + + Transferable t = c.getContents(this); + if(t==null) + { + this.getToolkit().beep(); + return; + } + try + { + if(t.isDataFlavorSupported(DataFlavor.stringFlavor)) + { + String s = (String) t.getTransferData(DataFlavor.stringFlavor); + this.replaceSelection(s); + } + else + this.getToolkit().beep(); + } + catch (UnsupportedFlavorException ex) { this.getToolkit().beep(); } + catch (IOException ex) { this.getToolkit().beep(); } + + } + + protected static Border dropBorder = new BevelBorder(BevelBorder.LOWERED); + protected static Border endBorder = + BorderFactory.createLineBorder(Color.black); + + public void dragEnter(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(DataFlavor.stringFlavor)) + { + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + this.setBorder(dropBorder); + } + } + + public void dragExit(DropTargetEvent e) + { + this.setBorder(null); + } + + public void drop(DropTargetDropEvent e) + { + this.setBorder(endBorder); + Transferable t = e.getTransferable(); + if(t.isDataFlavorSupported(DataFlavor.stringFlavor)) + { + e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); + try + { + String dropS = (String)t.getTransferData(DataFlavor.stringFlavor); + this.replaceSelection(dropS); + e.dropComplete(true); + } + catch (Exception ex) {} + } + else + { + e.rejectDrop(); + return; + } + return; + } + + public void dragOver(DropTargetDragEvent e) + { + if(e.isDataFlavorSupported(DataFlavor.stringFlavor)) + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + } + public void dropActionChanged(DropTargetDragEvent e) {} +} + + + + diff --git a/uk/ac/sanger/artemis/components/TextRequester.java b/uk/ac/sanger/artemis/components/TextRequester.java new file mode 100644 index 000000000..ddf3bc6e8 --- /dev/null +++ b/uk/ac/sanger/artemis/components/TextRequester.java @@ -0,0 +1,158 @@ +/* TextRequester.java + * + * created: Mon Jan 11 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextRequester.java,v 1.1 2004-06-09 09:47:54 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; + +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; + +/** + * A popup JTextField with an OK and a Cancel button. + * + * @author Kim Rutherford + * @version $Id: TextRequester.java,v 1.1 2004-06-09 09:47:54 tjc Exp $ + **/ + +public class TextRequester extends JFrame { + /** + * Create a new TextRequester component with the given prompt. Other + * components can listen for TextRequesterEvent object. + * @param prompt A message that is displayed in the component beside the + * TextArea that the user types into. This String is also used as the + * JFrame title. + * @param width The width of the JTextField in the new requester. + * @param initial_text The initial text to put in the JTextField. + **/ + public TextRequester (final String prompt, + final int width, + final String initial_text) { + super (prompt); + + getContentPane ().add (new JLabel (prompt), "North"); + + final JPanel panel = new JPanel (); + + panel.add (ok_button); + ok_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + performOK (); + } + }); + + panel.add (cancel_button); + cancel_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + performCancel (); + } + }); + + getContentPane ().add (panel, "South"); + + text_field = new JTextField (initial_text, width); + + text_field.addKeyListener (new KeyAdapter () { + public void keyTyped(final KeyEvent e) { + if (e.getKeyChar () == '\n') { + performOK (); + } + } + }); + + getContentPane ().add (text_field, "Center"); + + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + } + + /** + * Add the given object as a listen for TextRequester events from this + * TextRequester. + **/ + public void addTextRequesterListener (final TextRequesterListener l) { + listeners.addElement (l); + } + + /** + * Return the text the is currently displayed in this requester. + **/ + protected String getText () { + return text_field.getText (); + } + + /** + * Send a TextRequesterEvent of type OK to all the listeners. + **/ + protected void performOK () { + final TextRequesterEvent new_event = + new TextRequesterEvent (this, getText (), TextRequesterEvent.OK); + + sendEvent (new_event); + + TextRequester.this.dispose (); + } + + /** + * Send a TextRequesterEvent of type CANCEL to all the listeners. + **/ + protected void performCancel () { + final TextRequesterEvent new_event = + new TextRequesterEvent (this, getText (), TextRequesterEvent.CANCEL); + + sendEvent (new_event); + + TextRequester.this.dispose (); + } + + /** + * Send the given TextRequesterEvent to all the object that are listening + * for the event. + **/ + private void sendEvent (final TextRequesterEvent event) { + for (int i = 0 ; i < listeners.size () ; ++i) { + final TextRequesterListener listener = + ((TextRequesterListener) listeners.elementAt (i)); + + listener.actionPerformed (event); + } + } + + private final JButton ok_button = new JButton ("OK"); + private final JButton cancel_button = new JButton ("Cancel"); + private JTextField text_field = null; + + /** + * This contains the objects that are listening for TextRequester events + * from this TextRequester. + **/ + private Vector listeners = new Vector (); +} diff --git a/uk/ac/sanger/artemis/components/TextRequesterEvent.java b/uk/ac/sanger/artemis/components/TextRequesterEvent.java new file mode 100644 index 000000000..b1e3951d0 --- /dev/null +++ b/uk/ac/sanger/artemis/components/TextRequesterEvent.java @@ -0,0 +1,87 @@ +/* TextRequesterEvent.java + * + * created: Wed Jan 13 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextRequesterEvent.java,v 1.1 2004-06-09 09:47:55 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +/** + * This event is sent when the user presses OK or Cancel in a TextRequester + * component. + * + * @author Kim Rutherford + * @version $Id: TextRequesterEvent.java,v 1.1 2004-06-09 09:47:55 tjc Exp $ + **/ + +public class TextRequesterEvent extends java.util.EventObject { + /** + * Event type - This event was generated by pressing the OK button. + **/ + final public static int OK = 1; + + /** + * Event type - This event was generated by pressing the CANCEL button. + **/ + final public static int CANCEL = 2; + + /** + * Create a new TextRequesterEvent object. + * @param source The TextRequester that generated the event. + * @param requester_text The contents of the TextField in the TextRequester. + * @param type The type of event. + **/ + public TextRequesterEvent (final TextRequester source, + final String requester_text, + final int type) { + super (source); + this.requester_text = requester_text; + this.type = type; + } + + /** + * Return the type of this event as passed to the constructor. + **/ + public int getType () { + return type; + } + + /** + * Return the TextRequester contents String that was passed to the + * constructor. + **/ + public String getRequesterText () { + return requester_text; + } + + /** + * This is the type of this event, as passed to the constructor + **/ + private int type; + + /** + * The TextRequester contents String that was passed to the constructor. + **/ + private String requester_text; +} + + diff --git a/uk/ac/sanger/artemis/components/TextRequesterListener.java b/uk/ac/sanger/artemis/components/TextRequesterListener.java new file mode 100644 index 000000000..0b70582f9 --- /dev/null +++ b/uk/ac/sanger/artemis/components/TextRequesterListener.java @@ -0,0 +1,44 @@ +/* TextRequesterListener.java + * + * created: Sun Jan 17 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextRequesterListener.java,v 1.1 2004-06-09 09:47:56 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +/** + * This interface is implemented by those classes that need to listen for + * TextRequesterEvents. + * + * @author Kim Rutherford + * @version $Id: TextRequesterListener.java,v 1.1 2004-06-09 09:47:56 tjc Exp $ + **/ + +public interface TextRequesterListener { + /** + * Invoked when the user presses the OK or Cancel button on a TextRequester + * component. + **/ + void actionPerformed (final TextRequesterEvent event); +} + + diff --git a/uk/ac/sanger/artemis/components/Utilities.java b/uk/ac/sanger/artemis/components/Utilities.java new file mode 100644 index 000000000..b5d90710c --- /dev/null +++ b/uk/ac/sanger/artemis/components/Utilities.java @@ -0,0 +1,144 @@ +/* Utilities.java + * + * created: Tue Sep 18 2001 + * + * This file is part of Artemis + * + * Copyright(C) 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/Utilities.java,v 1.1 2004-06-09 09:47:57 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.util.InputStreamProgressListener; +import uk.ac.sanger.artemis.EntrySourceVector; +import uk.ac.sanger.artemis.Options; + +import java.awt.*; +import java.net.MalformedURLException; +import javax.swing.*; + +/** + * Utilities methods used by the uk.ac.sanger.artemis.components package. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: Utilities.java,v 1.1 2004-06-09 09:47:57 tjc Exp $ + **/ + +public class Utilities +{ + /** + * Return the JFrame that contains the given component. + **/ + public static JFrame getComponentFrame(final JComponent component) + { + if(component.getTopLevelAncestor() instanceof JFrame) + return(JFrame) component.getTopLevelAncestor(); + else + return null; + } + + /** + * Move the given JFrame to the centre of the screen. + **/ + public static void centreFrame(final JFrame frame) + { + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + final int x_position =(screen.width - frame.getSize().width) / 2; + int y_position =(screen.height - frame.getSize().height) / 2; + + if(y_position < 10) + y_position = 10; + + frame.setLocation(new Point(x_position, y_position)); + } + + /** + * Find the parent Frame of the given component and re-centre it on the + * screen. + **/ + public static void centreOwningFrame(final JComponent component) + { + final JFrame frame = getComponentFrame(component); + centreFrame(frame); + } + + /** + * Returns a Vector containing the possible Entry sources for this + * application. + * @param frame The component that is creating the EntrySource objects. + * (Used for requesters.) + * @param listener InputStreamProgressEvent objects will be sent to this + * listener as progress on reading is made. + **/ + public static EntrySourceVector getEntrySources(final JFrame frame, + final InputStreamProgressListener listener) + { + final EntrySourceVector return_vector = new EntrySourceVector(); + + return_vector.add(new FileDialogEntrySource(frame, listener)); + return_vector.add(new DbfetchEntrySource(frame)); + + // return_vector.add(new BioJavaEntrySource()); + + // this doesn't work on a v1.2 system so it is taken out with perl when + // necessary + // CORBA_START_MARKER + + // The location of the IOR for the corba server at EMBL. Can be + // overridden using the options file. + final String embl_ior_url = + Options.getOptions().getProperty("embl_ior_url"); + + if(embl_ior_url != null) + { + try + { + return_vector.add(new EMBLCorbaEntrySource(frame, embl_ior_url)); + } + catch(MalformedURLException e) + { + new MessageDialog(frame, "the url given for the embl database is " + + "badly formatted: " + e.getMessage()); + } + } + + // The location of the IOR for the pathogens group read-write corba + // server. + final String db_ior_url = + Options.getOptions().getProperty("db_ior_url"); + + if(db_ior_url != null) + { + try + { + return_vector.add(new WritableEMBLCorbaEntrySource(frame, + db_ior_url)); + } + catch(MalformedURLException e) + { + new MessageDialog(frame, "the url given for the embl database is " + + "badly formatted: " + e.getMessage()); + } + } + // CORBA_END_MARKER + + return return_vector; + } +} diff --git a/uk/ac/sanger/artemis/components/ViewMenu.java b/uk/ac/sanger/artemis/components/ViewMenu.java new file mode 100644 index 000000000..f39c5c918 --- /dev/null +++ b/uk/ac/sanger/artemis/components/ViewMenu.java @@ -0,0 +1,1430 @@ +/* ViewMenu.java + * + * created: Tue Dec 29 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000,2002 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/ViewMenu.java,v 1.1 2004-06-09 09:47:58 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; +import uk.ac.sanger.artemis.plot.*; + +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.InvalidRelationException; +import uk.ac.sanger.artemis.io.Key; +import uk.ac.sanger.artemis.io.InvalidKeyException; +import uk.ac.sanger.artemis.io.Range; + +import java.io.*; +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A popup menu with viewing commands. + * + * @author Kim Rutherford + * @version $Id: ViewMenu.java,v 1.1 2004-06-09 09:47:58 tjc Exp $ + **/ + +public class ViewMenu extends SelectionMenu { + /** + * Create a new ViewMenu object. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group The EntryGroup object where new features/entries will + * be added. + * @param goto_event_source The object that we will call makeBaseVisible () + * on. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + * @param menu_name The name of the new menu. + **/ + public ViewMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group, + final String menu_name) { + super (frame, menu_name, selection); + + this.entry_group = entry_group; + this.selection = selection; + this.goto_event_source = goto_event_source; + + this.base_plot_group = base_plot_group; + + plot_features_item = new JMenuItem ("Show Feature Plots"); + plot_features_item.setAccelerator (PLOT_FEATURES_KEY); + plot_features_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + plotSelectedFeatures (getParentFrame (), getSelection ()); + } + }); + + view_feature_item = new JMenuItem ("View Selected Features"); + view_feature_item.setAccelerator (VIEW_FEATURES_KEY); + view_feature_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + viewSelectedFeatures (getParentFrame (), getSelection ()); + } + }); + + view_selection_item = new JMenuItem ("View Selection"); + view_selection_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + new SelectionViewer (getSelection (), entry_group); + } + }); + + feature_info_item = new JMenuItem ("Show Feature Statistics"); + feature_info_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + viewSelectedFeatureInfo (); + } + }); + + view_bases_item = new JMenuItem ("View Bases Of Selection"); + view_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + viewSelectedBases (true); + } + }); + + view_bases_as_fasta_item = + new JMenuItem ("View Bases Of Selection As FASTA"); + view_bases_as_fasta_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + viewSelectedBases (false); + } + }); + + view_aa_item = new JMenuItem ("View Amino Acids Of Selection"); + view_aa_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + viewSelectedAminoAcids (true); + } + }); + + view_aa_as_fasta_item = + new JMenuItem ("View Amino Acids Of Selection As FASTA"); + view_aa_as_fasta_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + viewSelectedAminoAcids (false); + } + }); + + overview_item = new JMenuItem ("Show Overview"); + overview_item.setAccelerator (OVERVIEW_KEY); + overview_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + new EntryGroupInfoDisplay (getParentFrame (), entry_group); + } + }); + + forward_overview_item = new JMenuItem ("Show Forward Strand Overview"); + forward_overview_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + new EntryGroupInfoDisplay (getParentFrame (), entry_group, + EntryGroupInfoDisplay.FORWARD); + } + }); + + reverse_overview_item = new JMenuItem ("Show Reverse Strand Overview"); + reverse_overview_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + new EntryGroupInfoDisplay (getParentFrame (), entry_group, + EntryGroupInfoDisplay.REVERSE); + } + }); + + view_cds_item = new JMenuItem ("Show CDS Genes And Products"); + view_cds_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + final FeaturePredicate feature_predicate = + new FeatureKeyPredicate (Key.CDS); + + final String filter_name = + "CDS features (filtered from: " + + getParentFrame ().getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_event_source, + filtered_entry_group, + base_plot_group); + + feature_list_frame.getFeatureList ().setShowGenes (true); + feature_list_frame.getFeatureList ().setShowProducts (true); + + feature_list_frame.setVisible (true); + } + }); + + JMenu search_results_menu = null; + + search_results_menu = new JMenu ("Search Results"); + + final boolean sanger_options = + Options.getOptions ().getPropertyTruthValue ("sanger_options"); + + final ExternalProgramVector external_programs = + Options.getOptions ().getExternalPrograms (); + + final StringVector external_program_names = new StringVector (); + + for (int i = 0 ; i < external_programs.size () ; ++i) { + final ExternalProgram external_program = + external_programs.elementAt (i); + + final String new_name = external_program.getName (); + + if (!external_program_names.contains (new_name)) { + external_program_names.add (new_name); + } + } + + for (int i = 0 ; i < external_program_names.size () ; ++i) { + final String external_program_name = + external_program_names.elementAt (i); + + final JMenuItem new_menu = + makeSearchResultsMenu (external_program_name, false, sanger_options); + search_results_menu.add (new_menu); + } + + if (sanger_options) { + search_results_menu.addSeparator (); + + for (int i = 0 ; i < external_program_names.size () ; ++i) { + final String external_program_name = + external_program_names.elementAt (i); + + final JMenuItem new_menu = + makeSearchResultsMenu (external_program_name, true, sanger_options); + search_results_menu.add (new_menu); + } + } + + final int MAX_FILTER_FEATURE_COUNT = 10000; + + final JMenu feature_filters_menu = new JMenu ("Feature Filters"); + + final JMenuItem bad_start_codons_item = + new JMenuItem ("Suspicious Start Codons ..."); + bad_start_codons_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showBadStartCodons (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem bad_stop_codons_item = + new JMenuItem ("Suspicious Stop Codons ..."); + bad_stop_codons_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showBadStopCodons (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem stop_codons_in_translation = + new JMenuItem ("Stop Codons In Translation ..."); + stop_codons_in_translation.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showStopsInTranslation (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem bad_feature_keys_item = + new JMenuItem ("Non EMBL Keys ..."); + bad_feature_keys_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showNonEMBLKeys (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem duplicated_keys_item = + new JMenuItem ("Duplicated Features ..."); + duplicated_keys_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showDuplicatedFeatures (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem overlapping_cds_features_item = + new JMenuItem ("Overlapping CDS Features ..."); + overlapping_cds_features_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showOverlappingCDSs (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem same_stop_cds_features_item = + new JMenuItem ("CDSs Sharing Stop Codons ..."); + same_stop_cds_features_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showFeaturesWithSameStopCodons (getParentFrame (), + selection, entry_group, + goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem missing_qualifier_features_item = + new JMenuItem ("Features Missing Required Qualifiers ..."); + missing_qualifier_features_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showMissingQualifierFeatures (getParentFrame (), + selection, + entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem filter_by_key_item = + new JMenuItem ("Filter By Key ..."); + filter_by_key_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + if (checkEntryGroupSize (MAX_FILTER_FEATURE_COUNT)) { + showFilterByKey (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + } + }); + + final JMenuItem filter_by_selection_item = + new JMenuItem ("Selected Features ..."); + filter_by_selection_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + showFilterBySelection (getParentFrame (), + selection, entry_group, goto_event_source, + base_plot_group); + } + }); + + feature_filters_menu.add (bad_start_codons_item); + feature_filters_menu.add (bad_stop_codons_item); + feature_filters_menu.add (stop_codons_in_translation); + feature_filters_menu.add (bad_feature_keys_item); + feature_filters_menu.add (duplicated_keys_item); + feature_filters_menu.add (overlapping_cds_features_item); + feature_filters_menu.add (same_stop_cds_features_item); + feature_filters_menu.add (missing_qualifier_features_item); + feature_filters_menu.addSeparator (); + feature_filters_menu.add (filter_by_key_item); + feature_filters_menu.add (filter_by_selection_item); + + add (view_feature_item); + add (view_selection_item); + addSeparator (); + if (search_results_menu != null) { + add (search_results_menu); + } + add (view_cds_item); + add (feature_filters_menu); + addSeparator (); + add (overview_item); + add (forward_overview_item); + add (reverse_overview_item); + addSeparator (); + add (view_bases_item); + add (view_bases_as_fasta_item); + add (view_aa_item); + add (view_aa_as_fasta_item); + addSeparator (); + add (feature_info_item); + add (plot_features_item); + } + + /** + * Create a new ViewMenu object. + * @param entry_edit The EntryEdit that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group The EntryGroup object where new features/entries will + * be added. + * @param goto_event_source The object that we will call makeBaseVisible () + * on. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public ViewMenu (final JFrame frame, + final Selection selection, + final GotoEventSource goto_event_source, + final EntryGroup entry_group, + final BasePlotGroup base_plot_group) { + this (frame, selection, goto_event_source, entry_group, + base_plot_group, "View"); + } + + /** + * The shortcut for Show Feature Plots. + **/ + final static KeyStroke PLOT_FEATURES_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_W, InputEvent.CTRL_MASK); + + final static public int PLOT_FEATURES_KEY_CODE = KeyEvent.VK_W; + + /** + * The shortcut for View Selected Features. + **/ + final static KeyStroke VIEW_FEATURES_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_V, InputEvent.CTRL_MASK); + + final static public int VIEW_FEATURES_KEY_CODE = KeyEvent.VK_V; + + /** + * The shortcut for Show Overview. + **/ + final static KeyStroke OVERVIEW_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_O, InputEvent.CTRL_MASK); + + final static public int OVERVIEW_KEY_CODE = KeyEvent.VK_O; + + /** + * The shortcut for View FASTA in browser. + **/ + final static KeyStroke FASTA_IN_BROWSER_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_F, InputEvent.CTRL_MASK); + + final static public int FASTA_IN_BROWSER_KEY_CODE = KeyEvent.VK_F; + + /** + * The shortcut for View FASTA. + **/ + final static KeyStroke VIEW_FASTA_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_R, InputEvent.CTRL_MASK); + + final static public int VIEW_FASTA_KEY_CODE = KeyEvent.VK_R; + + /** + * The shortcut for View BLASTP in browser. + **/ + final static KeyStroke BLASTP_IN_BROWSER_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_B, InputEvent.CTRL_MASK); + + final static public int BLASTP_IN_BROWSER_KEY_CODE = KeyEvent.VK_B; + + /** + * The shortcut for View BLASTP. + **/ + final static KeyStroke VIEW_BLASTP_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_BACK_QUOTE , InputEvent.CTRL_MASK); + + final static public int VIEW_BLASTP_KEY_CODE = KeyEvent.VK_BACK_QUOTE; + + /** + * The shortcut for View HTH. + **/ + final static KeyStroke VIEW_HTH_KEY = + KeyStroke.getKeyStroke (KeyEvent.VK_H, InputEvent.CTRL_MASK); + + final static public int VIEW_HTH_KEY_CODE = KeyEvent.VK_H; + + /** + * Make a JMenuItem for viewing the results of running the given program. + * @param send_to_browser if true the results should be sent straight to + * the web browser rather than using a SearchResultViewer object. + * @param sanger_options true if the sanger_options is set to true in the + * options file. + **/ + private JMenuItem makeSearchResultsMenu (final String program_name, + final boolean send_to_browser, + final boolean sanger_options) { + final String new_menu_name; + + if (send_to_browser) { + new_menu_name = program_name + " results (in browser)"; + } else { + new_menu_name = program_name + " results"; + } + + final JMenuItem new_menu; + + if ((sanger_options && send_to_browser || !sanger_options) + && program_name.equals ("fasta")) { + new_menu = new JMenuItem (new_menu_name); + new_menu.setAccelerator (FASTA_IN_BROWSER_KEY); + } else { + if ((sanger_options && send_to_browser || !sanger_options) + && program_name.equals ("blastp")) { + new_menu = new JMenuItem (new_menu_name); + new_menu.setAccelerator (BLASTP_IN_BROWSER_KEY); + } else { + if (program_name.equals ("fasta")) { + new_menu = new JMenuItem (new_menu_name); + new_menu.setAccelerator (VIEW_FASTA_KEY); + } else { + if (program_name.equals ("blastp")) { + new_menu = new JMenuItem (new_menu_name); + new_menu.setAccelerator (VIEW_BLASTP_KEY); + } else { + if (program_name.equals ("hth")) { + new_menu = new JMenuItem (new_menu_name); + new_menu.setAccelerator (VIEW_HTH_KEY); + } else { + new_menu = new JMenuItem (new_menu_name); + } + } + } + } + } + + new_menu.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + viewExternalResults (getParentFrame (), getSelection (), + program_name, send_to_browser); + } + }); + + return new_menu; + } + + /** + * Popup a FeatureListFrame containing the non-pseudo CDS features that + * have invalid start codons. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showBadStartCodons (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup + base_plot_group) { + final FeatureKeyQualifierPredicate cds_predicate = + new FeatureKeyQualifierPredicate (Key.CDS, "pseudo", false); + + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (!cds_predicate.testPredicate (feature)) { + return false; + } + + if (feature.hasValidStartCodon ()) { + return false; + } else { + return true; + } + } + }; + + final String filter_name = + "CDS features with suspicious start codons (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing the non-pseudo CDS features that + * have invalid stop codons. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showBadStopCodons (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup + base_plot_group) { + final FeatureKeyQualifierPredicate cds_predicate = + new FeatureKeyQualifierPredicate (Key.CDS, "pseudo", false); + + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (!cds_predicate.testPredicate (feature)) { + return false; + } + + if (feature.hasValidStopCodon ()) { + return false; + } else { + return true; + } + } + }; + + final String filter_name = + "CDS features with suspicious stop codons (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing the non-pseudo CDS features that + * contain a stop codon in the translation. + * @param parent_frame The parent Frame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this Menu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showStopsInTranslation (final Frame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup + base_plot_group) { + final FeatureKeyQualifierPredicate cds_predicate = + new FeatureKeyQualifierPredicate (Key.CDS, "pseudo", false); + + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (!cds_predicate.testPredicate (feature)) { + return false; + } + + final AminoAcidSequence amino_acids = feature.getTranslation (); + + if (amino_acids.containsStopCodon ()) { + return true; + } else { + return false; + } + } + }; + + final String filter_name = + "CDS features with stop codon(s) in translation (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing the features that have non-EMBL keys. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showNonEMBLKeys (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup + base_plot_group) { + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (feature.hasValidEMBLKey ()) { + return false; + } else { + return true; + } + } + }; + + final String filter_name = + "features with a non-EMBL key (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + + /** + * Popup a FeatureListFrame containing the features that have the same key + * and location as another features (ie. duplicates). + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showDuplicatedFeatures (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup + base_plot_group) { + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + final Entry feature_entry = feature.getEntry (); + + final int feature_index = feature_entry.indexOf (feature); + + if (feature_index + 1 == feature_entry.getFeatureCount ()) { + // last in the Entry + return false; + } + + final Feature next_feature = + feature_entry.getFeature (feature_index + 1); + + if (feature.getKey ().equals (next_feature.getKey ()) && + feature.getLocation ().equals (next_feature.getLocation ())) { + return true; + } else { + return false; + } + } + }; + + final String filter_name = + "duplicated Features (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing those CDS features that overlap with + * the next feature. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showOverlappingCDSs (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup + base_plot_group) { + final FeatureKeyPredicate cds_predicate = + new FeatureKeyPredicate (Key.CDS); + + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature test_feature) { + if (!cds_predicate.testPredicate (test_feature)) { + return false; + } + + final Range feature_range = test_feature.getMaxRawRange (); + + final FeatureVector overlapping_features; + + try { + overlapping_features = + entry_group.getFeaturesInRange (feature_range); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + for (int i = 0 ; i < overlapping_features.size () ; ++i) { + final Feature current_feature = overlapping_features.elementAt (i); + + if (current_feature != test_feature && + cds_predicate.testPredicate (current_feature)) { + return true; + } + } + + return false; + } + }; + + final String filter_name = + "overlapping CDS features (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing those CDS features that overlap with + * the next feature. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void + showFeaturesWithSameStopCodons (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup base_plot_group) { + final FeatureKeyPredicate cds_predicate = + new FeatureKeyPredicate (Key.CDS); + + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature test_feature) { + if (!cds_predicate.testPredicate (test_feature)) { + return false; + } + + final Range feature_range = test_feature.getMaxRawRange (); + + final FeatureVector overlapping_features; + + try { + overlapping_features = + entry_group.getFeaturesInRange (feature_range); + } catch (OutOfRangeException e) { + throw new Error ("internal error - unexpected exception: " + e); + } + + for (int i = 0 ; i < overlapping_features.size () ; ++i) { + final Feature current_feature = overlapping_features.elementAt (i); + + if (current_feature == test_feature) { + continue; + } + + if (current_feature != test_feature && + cds_predicate.testPredicate (current_feature) && + (current_feature.getLastBase () == + test_feature.getLastBase ()) && + (current_feature.getSegments ().lastElement ().getFrameID () == + test_feature.getSegments ().lastElement ().getFrameID ())) { + return true; + } + } + + return false; + } + }; + + final String filter_name = + "CDS features with the same stop codon as another (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing the features that are missing + * required EMBL qualifiers. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showMissingQualifierFeatures (final JFrame parent_frame, + final Selection selection, + final EntryGroup + entry_group, + final GotoEventSource + goto_source, + final BasePlotGroup + base_plot_group) { + final FeaturePredicate feature_predicate = + new FeaturePredicate () { + public boolean testPredicate (final Feature feature) { + if (feature.hasRequiredQualifiers ()) { + return false; + } else { + return true; + } + } + }; + + final String filter_name = + "features that are missing a required EMBL " + + "qualifier (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, feature_predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing only those features that have the + * key choosen by the user. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showFilterByKey (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource goto_source, + final BasePlotGroup base_plot_group) { + final KeyChooser key_chooser; + + key_chooser = new KeyChooser (Options.getArtemisEntryInformation (), + new Key ("misc_feature")); + + key_chooser.getKeyChoice ().addItemListener (new ItemListener () { + public void itemStateChanged (ItemEvent e) { + if (e.getStateChange () == ItemEvent.SELECTED) { + showFilterByKeyHelper (parent_frame, + key_chooser.getKeyChoice ().getSelectedItem (), + selection, entry_group, goto_source, + base_plot_group); + key_chooser.setVisible (false); + key_chooser.dispose (); + } + } + }); + + key_chooser.getOKButton ().addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent _) { + showFilterByKeyHelper (parent_frame, + key_chooser.getKeyChoice ().getSelectedItem (), + selection, entry_group, goto_source, + base_plot_group); + key_chooser.setVisible (false); + key_chooser.dispose (); + } + }); + + key_chooser.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing only those features that have the + * key choosen by the user. + * @param parent_frame The parent JFrame. + * @param key The key to use in the filter. + * @param selection The Selection to pass to the FeatureList. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showFilterByKeyHelper (final JFrame parent_frame, + final Key key, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource + goto_source, + final BasePlotGroup + base_plot_group) { + final String filter_name = + "features with key: " + key + " (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, + new FeatureKeyPredicate (key), filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * Popup a FeatureListFrame containing only those features that are + * currently in the Selection. + * @param parent_frame The parent JFrame. + * @param selection The Selection to pass to the FeatureList and to use to + * filter the entry_group. + * @param entry_group The EntryGroup to pass to the FilteredEntryGroup. + * @param goto_source The GotoEventSource to pass to the FeatureList. + * @param base_plot_group The BasePlotGroup associated with this JMenu - + * needed to call getCodonUsageAlgorithm() + **/ + public static void showFilterBySelection (final JFrame parent_frame, + final Selection selection, + final EntryGroup entry_group, + final GotoEventSource + goto_source, + final BasePlotGroup + base_plot_group) { + final FeaturePredicate predicate = + new FeatureFromVectorPredicate (selection.getAllFeatures ()); + + final String filter_name = + "features from the selection (filtered from: " + + parent_frame.getTitle () + ")"; + + final FilteredEntryGroup filtered_entry_group = + new FilteredEntryGroup (entry_group, predicate, filter_name); + + final FeatureListFrame feature_list_frame = + new FeatureListFrame (filter_name, + selection, goto_source, filtered_entry_group, + base_plot_group); + + feature_list_frame.setVisible (true); + } + + /** + * viewSelectedFeatures(), plotSelectedFeatures() etc. will only show this + * many features. + **/ + private static final int MAXIMUM_SELECTED_FEATURES = 25; + + /** + * Open a view window for each of the selected features. The viewer will + * listen for feature change events and update itself. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the features to merge. + **/ + static void viewSelectedFeatures (final JFrame frame, + final Selection selection) { + final FeatureVector features_to_view = selection.getAllFeatures (); + + if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) { + new MessageDialog (frame, "warning: only viewing the first " + + MAXIMUM_SELECTED_FEATURES + " selected features"); + } + + for (int i = 0 ; + i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ; + ++i) { + final Feature selection_feature = features_to_view.elementAt (i); + + new FeatureViewer (selection_feature); + } + } + + /** + * Open an plot viewing window for each of the selected features. The plot + * viewer will listen for feature change events and update itself. + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the features to plot. + **/ + static void plotSelectedFeatures (final JFrame frame, + final Selection selection) { + final FeatureVector features_to_plot = selection.getAllFeatures (); + + if (features_to_plot.size () > MAXIMUM_SELECTED_FEATURES) { + new MessageDialog (frame, "warning: only showing plots for the first " + + MAXIMUM_SELECTED_FEATURES + " selected features"); + } + + for (int i = 0 ; + i < features_to_plot.size () && i < MAXIMUM_SELECTED_FEATURES ; + ++i) { + final Feature selection_feature = features_to_plot.elementAt (i); + + new FeaturePlotGroup (selection_feature); + } + } + + /** + * Show the output file from an external program (like fasta) for the + * selected Feature objects. The name of the file to read is stored in a + * feature qualifier. The qualifier used is the program name plus "_file". + * @param frame The JFrame to use for MessageDialog components. + * @param selection The Selection containing the features to merge. + * @param send_to_browser if true the results should be sent straight to + * the web browser rather than using a SearchResultViewer object. + **/ + static void viewExternalResults (final JFrame frame, + final Selection selection, + final String program_name, + final boolean send_to_browser) { + final FeatureVector features_to_view = selection.getAllFeatures (); + + if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) { + new MessageDialog (frame, "warning: only viewing results from " + + "the first " + MAXIMUM_SELECTED_FEATURES + + " selected features"); + } + + for (int i = 0 ; + i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ; + ++i) { + final Feature this_feature = features_to_view.elementAt (i); + + String qualifier_value; + + try { + qualifier_value = + this_feature.getValueOfQualifier (program_name + "_file"); + } catch (InvalidRelationException e) { + qualifier_value = null; + } + + String file_name = qualifier_value; + + if (file_name == null || file_name.length () == 0) { + new MessageDialog (frame, + "Message", + "No " + program_name + " results for " + + this_feature.getIDString ()); + + continue; + } + + // remove the program name string (if any) from the qualifier, so that + // we can try to read the file with and without the prefix. + if (file_name.startsWith (program_name + File.separatorChar)) { + file_name = file_name.substring (program_name.length () + 1); + } + + Document root_document = this_feature.getEntry ().getRootDocument (); + + if (root_document == null) { + root_document = new FileDocument (new File (".")); + } + + try { + Document document = null; + + // try the current directory with the program name added: + // ./fasta/abc.seq.00001.out + final File dir_name = new File (program_name); + + final Document [] possible_documents = + new Document [] { + root_document.append (program_name).append (file_name), + root_document.append (file_name), + new FileDocument (new File (file_name)), + new FileDocument (dir_name).append (file_name), + }; + + for (int possible_document_index = 0 ; + possible_document_index < possible_documents.length ; + ++possible_document_index) { + + final Document this_document = + possible_documents[possible_document_index]; + + if (this_document.readable ()) { + document = this_document; + break; + } else { + final File gzip_file = + new File (this_document.toString () + ".gz"); + final Document gzip_document = + new FileDocument (gzip_file); + + if (gzip_document.readable ()) { + document = gzip_document; + break; + } + } + } + + if (document == null) { + final String message_string = + "No " + program_name + " results for " + + this_feature.getIDString () + " (file not found: " + + qualifier_value + ")"; + + new MessageDialog (frame, message_string); + + continue; + } + + if (send_to_browser) { + SearchResultViewer.sendToBrowser (document.toString ()); + } else { + new SearchResultViewer (program_name + " results for " + + this_feature.getIDString () + " from " + + document, + document); + } + } catch (ExternalProgramException e) { + new MessageDialog (frame, + "error while open results file: " + e); + } catch (IOException e) { + new MessageDialog (frame, + "error while open results file: " + e); + } + } + } + + /** + * Open a FeatureInfo component for each of the selected features. The + * new component will listen for feature change events and update itself. + **/ + private void viewSelectedFeatureInfo () { + final FeatureVector features_to_view = getSelection ().getAllFeatures (); + + if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) { + new MessageDialog (getParentFrame (), + "warning: only viewing the statistics for " + + "the first " + MAXIMUM_SELECTED_FEATURES + + " selected features"); + } + + for (int i = 0 ; + i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ; + ++i) { + final Feature selection_feature = features_to_view.elementAt (i); + + new FeatureInfo (selection_feature, + base_plot_group.getCodonUsageAlgorithm ()); + } + } + + /** + * View the bases of the selected features. This creates a + * FeatureBaseViewer for each feature. + * @param include_numbers If true then the sequence will be numbered + * (every second line of the display will be numbers rather than + * sequence). + **/ + private void viewSelectedBases (final boolean include_numbers) { + if (getSelection ().isEmpty ()) { + new MessageDialog (getParentFrame (), "Nothing selected"); + return; + } + + final MarkerRange range = selection.getMarkerRange (); + + if (range == null) { + final FeatureVector features_to_view = getSelection ().getAllFeatures (); + + if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) { + new MessageDialog (getParentFrame (), + "waning: only viewing bases for " + + "the first " + MAXIMUM_SELECTED_FEATURES + + " selected features"); + } + + for (int i = 0 ; + i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ; + ++i) { + final Feature this_feature = features_to_view.elementAt (i); + + new FeatureBaseViewer (this_feature, include_numbers); + } + } else { + final SequenceViewer sequence_viewer = + new SequenceViewer ("Selected bases", include_numbers); + + final String bases = getSelection ().getSelectionText (); + sequence_viewer.setSequence (null, bases); + } + } + + /** + * View the bases of the selected CDS features. This creates a + * FeatureBaseViewer for each CDS feature. + * @param include_numbers If true then the amino acids will be numbered + * (every second line of the display will be numbers rather than + * sequence). + **/ + private void viewSelectedAminoAcids (final boolean include_numbers) { + if (getSelection ().isEmpty ()) { + new MessageDialog (getParentFrame (), "Nothing selected"); + return; + } + + final MarkerRange range = selection.getMarkerRange (); + + if (range == null) { + final FeatureVector features_to_view = getSelection ().getAllFeatures (); + + if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) { + new MessageDialog (getParentFrame (), + "warning: only viewing amino acids for " + + "the first " + MAXIMUM_SELECTED_FEATURES + + " selected features"); + } + + for (int i = 0 ; + i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ; + ++i) { + final Feature this_feature = features_to_view.elementAt (i); + + new FeatureAminoAcidViewer (this_feature, include_numbers); + } + } else { + final SequenceViewer sequence_viewer = + new SequenceViewer ("Selected bases (translated)", include_numbers); + + final String bases = getSelection ().getSelectionText (); + + final AminoAcidSequence amino_acids = + AminoAcidSequence.getTranslation (bases, true); + + sequence_viewer.setSequence (null, amino_acids.toString ()); + } + } + + /** + * Check that the number of features in the EntryGroup is less than the + * given number. If it is less return true. If not then popup a + * YesNoDialog asking for confirmation and return true if and only if the + * user chooses yes. + **/ + private boolean checkEntryGroupSize (final int max_size) { + final int feature_count = getEntryGroup ().getAllFeaturesCount (); + + if (feature_count < max_size) { + return true; + } else { + final YesNoDialog dialog = + new YesNoDialog (getParentFrame (), + "there are " + feature_count + " features in the " + + "active entries - continue?"); + + return dialog.getResult (); + } + } + + /** + * Return the EntryGroup that was passed to the constructor. + **/ + private EntryGroup getEntryGroup () { + return entry_group; + } + + private JMenuItem feature_info_item = null; + private JMenuItem plot_features_item = null; + private JMenuItem view_feature_item = null; + private JMenuItem view_selection_item = null; + private JMenuItem view_fasta_item = null; + private JMenuItem view_bases_item = null; + private JMenuItem view_bases_as_fasta_item = null; + private JMenuItem view_aa_item = null; + private JMenuItem view_aa_as_fasta_item = null; + private JMenuItem overview_item = null; + private JMenuItem forward_overview_item = null; + private JMenuItem reverse_overview_item = null; + private JMenuItem view_cds_item = null; + + /** + * The EntryGroup that was passed to the constructor. + **/ + private EntryGroup entry_group = null; + + /** + * The Selection that was passed to the constructor. + **/ + private Selection selection = null; + + /** + * The GotoEventSource that was passed to the constructor. + **/ + private GotoEventSource goto_event_source = null; + + private BasePlotGroup base_plot_group; +} diff --git a/uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java b/uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java new file mode 100644 index 000000000..87ff712d3 --- /dev/null +++ b/uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java @@ -0,0 +1,226 @@ +/* WritableEMBLCorbaEntrySource.java + * + * created: Mon Jun 12 2000 + * + * This file is part of Artemis + * + * Copyright (C) 2000,2002x 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/WritableEMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:47:59 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.util.*; +import uk.ac.sanger.artemis.io.LocationParseException; +import uk.ac.sanger.artemis.io.InvalidKeyException; + +import nsdb.ServerInfo; +import nsdb.EntryStats; +import nsdb.EmblSeq; +import nsdb.EmblPackage.Superceded; +import type.NoResult; + +import java.io.*; +import java.awt.*; +import java.net.*; + +import javax.swing.*; + +/** + * This is an EntrySource that can get writable Entry objects from an EMBL + * CORBA server. + * + * @author Kim Rutherford <kmr@sanger.ac.uk> + * @version $Id: WritableEMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:47:59 tjc Exp $ + **/ + +public class WritableEMBLCorbaEntrySource extends EMBLCorbaEntrySource { + /** + * Create a new WritableEMBLCorbaEntrySource from the given String. + * @param frame The component that created this EntrySource. (Used for + * requesters.) + * @param ior_url_string A String containing the URL of the IOR for the + * server. + **/ + public WritableEMBLCorbaEntrySource (final JFrame frame, + final String ior_url_string) + throws MalformedURLException { + super (frame, ior_url_string); + } + + /** + * Get an Entry object from the Ensembl CORBA server. + * @param bases The Bases object to pass to the Entry constructor. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final Bases bases, final boolean show_progress) + throws OutOfRangeException, IOException { + return makeCorbaDialog (bases, false); + } + + /** + * Get an Entry object from the Ensembl CORBA server. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + * @exception NoSequenceException Thrown if the entry that we read has no + * sequence. + * @param show_progress If true a InputStreamProgressDialog will be shown + * while loading. + * @return null if and only if the user cancels the read or if the read + * fails. + **/ + public Entry getEntry (final boolean show_progress) + throws OutOfRangeException, NoSequenceException, IOException { + return makeCorbaDialog (null, false); + } + + /** + * Create a TextRequester, wait for the user to type an accession number + * and then read that entry from the EMBL CORBA server. + * @param bases If this is null a new Bases object will be created for the + * Entry once it has been read from the server. If not null then it will + * be passed to the Entry constructor. + * @param read_only true if and only if a read-only Entry should be created + * (some are always read only). + * @return null if and only if the user cancels the read or if the read + * fails. + * @exception OutOfRangeException Thrown if one of the features in + * the Entry is out of range of the Bases object. + **/ + protected Entry makeCorbaDialog (final Bases bases, + final boolean read_only) + throws OutOfRangeException, IOException { + final org.omg.CORBA.ORB orb = + org.omg.CORBA.ORB.init (new String [0], new java.util.Properties()); + + final nsdb.Embl corba_handle = getServerHandle (); + + final nsdb.EmblWriter embl_writer = + nsdb.EmblWriterHelper.narrow (corba_handle); + + if (embl_writer == null) { + final String message = "Server reference is not an EmblWriter: " + + corba_handle; + new MessageDialog (getFrame (), message); + return null; + } + + final ListDialog list_dialog = + new ListDialog (getFrame (), "Select an entry"); + + final ServerInfo stats = embl_writer.getServerInfo (); + + final EntryStats [] entry_stats_list = stats.entry_stats_list; + final EntryStats [] file_stats_list = stats.file_stats_list; + + list_dialog.getList ().setModel (new AbstractListModel () { + public int getSize () { + return entry_stats_list.length + file_stats_list.length; + } + public Object getElementAt (int i) { + if (i < entry_stats_list.length) { + return makeListString (entry_stats_list[i], false); + } else { + return makeListString (file_stats_list[i-entry_stats_list.length], + true); + } + } + }); + + // returns when user hits ok or cancel + final String selected_entry_string = + (String) list_dialog.getSelectedValue (); + + if (selected_entry_string == null) { + // user hit cancel + return null; + } + + final int end_of_entry_name = selected_entry_string.indexOf (" "); + + final String corba_id = + selected_entry_string.substring (0, end_of_entry_name); + + if (corba_id.length () > 0) { + final MessageDialog message_frame = + new MessageDialog (getFrame (), + "reading entry - please wait", false); + + try { + return makeFromCorbaID (bases, corba_id, read_only); + } finally { + message_frame.dispose (); + } + } else { + return null; + } + } + + /** + * Make a ListDialog then let the user choose an entry. + **/ + private String makeListString (final EntryStats this_entry_stats, + final boolean is_file_entry) { + final StringBuffer buffer = new StringBuffer (); + + buffer.append (this_entry_stats.name).append (" "); + + for (int j = 0 ; j < (30 - this_entry_stats.name.length ()) ; ++j) { + buffer.append (" "); + } + + final long last_change_time = this_entry_stats.last_change_time; + + if (is_file_entry) { + buffer.append ("(file)"); + } else { + if (last_change_time > 0) { + buffer.append (new java.util.Date (last_change_time * 1000L)); + } else { + buffer.append ("(not saved)"); + } + } + + return buffer.toString (); + } + + /** + * Return the name of this source (for display to the user in menus). + **/ + public String getSourceName () { + return "Database"; + } + + /** + * Returns true if and only if this EntrySource always returns "full" + * entries. ie. entries that contain features and sequence. Entries that + * are read from a file may contain just features so in this class this + * method returns false. + **/ + public boolean isFullEntrySource () { + return false; + } +} diff --git a/uk/ac/sanger/artemis/components/WriteMenu.java b/uk/ac/sanger/artemis/components/WriteMenu.java new file mode 100644 index 000000000..32c5a7402 --- /dev/null +++ b/uk/ac/sanger/artemis/components/WriteMenu.java @@ -0,0 +1,852 @@ +/* WriteMenu.java + * + * created: Mon Jan 11 1999 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/WriteMenu.java,v 1.1 2004-06-09 09:48:00 tjc Exp $ + **/ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.*; +import uk.ac.sanger.artemis.sequence.*; + +import uk.ac.sanger.artemis.io.Sequence; +import uk.ac.sanger.artemis.io.StreamSequence; +import uk.ac.sanger.artemis.io.FastaStreamSequence; +import uk.ac.sanger.artemis.io.RawStreamSequence; +import uk.ac.sanger.artemis.io.EmblStreamSequence; +import uk.ac.sanger.artemis.io.GenbankStreamSequence; +import uk.ac.sanger.artemis.io.StreamSequenceFactory; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; + +import javax.swing.*; + +/** + * A menu of commands for writing out protein and bases. + * + * @author Kim Rutherford + * @version $Id: WriteMenu.java,v 1.1 2004-06-09 09:48:00 tjc Exp $ + **/ + +public class WriteMenu extends SelectionMenu { + /** + * Create a new WriteMenu component. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group The EntryGroup object to use when writing a sequence. + * @param menu_name The name of the new menu. + **/ + public WriteMenu (final JFrame frame, + final Selection selection, + final EntryGroup entry_group, + final String menu_name) { + super (frame, menu_name, selection); + + this.entry_group = entry_group; + + final JMenuItem aa_item = new JMenuItem ("Amino Acids Of Selected Features"); + aa_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeAminoAcids (); + } + }); + + add (aa_item); + + final JMenuItem pir_item = + new JMenuItem ("PIR Database Of Selected Features"); + pir_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writePIRDataBase (); + } + }); + + add (pir_item); + + addSeparator (); + + final JMenu bases_menu = new JMenu ("Bases Of Selection"); + + final JMenuItem raw_bases_item = new JMenuItem ("Raw Format"); + raw_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeBasesOfSelection (StreamSequenceFactory.RAW_FORMAT); + } + }); + + bases_menu.add (raw_bases_item); + + final JMenuItem fasta_bases_item = new JMenuItem ("FASTA Format"); + fasta_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeBasesOfSelection (StreamSequenceFactory.FASTA_FORMAT); + } + }); + + bases_menu.add (fasta_bases_item); + + final JMenuItem embl_bases_item = new JMenuItem ("EMBL Format"); + embl_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeBasesOfSelection (StreamSequenceFactory.EMBL_FORMAT); + } + }); + + bases_menu.add (embl_bases_item); + + final JMenuItem genbank_bases_item = new JMenuItem ("Genbank Format"); + genbank_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeBasesOfSelection (StreamSequenceFactory.GENBANK_FORMAT); + } + }); + + bases_menu.add (genbank_bases_item); + + add (bases_menu); + + final JMenu upstream_bases_menu = + new JMenu ("Upstream Bases Of Selected Features"); + + final JMenuItem raw_upstream_bases_item = new JMenuItem ("Raw Format"); + raw_upstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeUpstreamBases (StreamSequenceFactory.RAW_FORMAT); + } + }); + + upstream_bases_menu.add (raw_upstream_bases_item); + + final JMenuItem fasta_upstream_bases_item = new JMenuItem ("FASTA Format"); + fasta_upstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeUpstreamBases (StreamSequenceFactory.FASTA_FORMAT); + } + }); + + upstream_bases_menu.add (fasta_upstream_bases_item); + + final JMenuItem embl_upstream_bases_item = new JMenuItem ("EMBL Format"); + embl_upstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeUpstreamBases (StreamSequenceFactory.EMBL_FORMAT); + } + }); + + upstream_bases_menu.add (embl_upstream_bases_item); + + final JMenuItem genbank_upstream_bases_item = new JMenuItem ("Genbank Format"); + genbank_upstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeUpstreamBases (StreamSequenceFactory.GENBANK_FORMAT); + } + }); + + upstream_bases_menu.add (genbank_upstream_bases_item); + + add (upstream_bases_menu); + + final JMenu downstream_bases_menu = + new JMenu ("Downstream Bases Of Selected Features"); + + final JMenuItem raw_downstream_bases_item = new JMenuItem ("Raw Format"); + raw_downstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeDownstreamBases (StreamSequenceFactory.RAW_FORMAT); + } + }); + + downstream_bases_menu.add (raw_downstream_bases_item); + + final JMenuItem fasta_downstream_bases_item = new JMenuItem ("FASTA Format"); + fasta_downstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeDownstreamBases (StreamSequenceFactory.FASTA_FORMAT); + } + }); + + downstream_bases_menu.add (fasta_downstream_bases_item); + + final JMenuItem embl_downstream_bases_item = new JMenuItem ("EMBL Format"); + embl_downstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeDownstreamBases (StreamSequenceFactory.EMBL_FORMAT); + } + }); + + downstream_bases_menu.add (embl_downstream_bases_item); + + final JMenuItem genbank_downstream_bases_item = + new JMenuItem ("Genbank Format"); + genbank_downstream_bases_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeDownstreamBases (StreamSequenceFactory.GENBANK_FORMAT); + } + }); + + downstream_bases_menu.add (genbank_downstream_bases_item); + + add (downstream_bases_menu); + + addSeparator (); + + final JMenu write_all_bases_menu = new JMenu ("Write All Bases"); + + final JMenuItem write_raw_item = new JMenuItem ("Raw Format"); + write_raw_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeAllSequence (StreamSequenceFactory.RAW_FORMAT); + } + }); + + write_all_bases_menu.add (write_raw_item); + + final JMenuItem write_fasta_item = new JMenuItem ("FASTA Format"); + write_fasta_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeAllSequence (StreamSequenceFactory.FASTA_FORMAT); + } + }); + + write_all_bases_menu.add (write_fasta_item); + + final JMenuItem write_embl_item = new JMenuItem ("EMBL Format"); + write_embl_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeAllSequence (StreamSequenceFactory.EMBL_FORMAT); + } + }); + + write_all_bases_menu.add (write_embl_item); + + final JMenuItem write_genbank_item = new JMenuItem ("Genbank Format"); + write_genbank_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeAllSequence (StreamSequenceFactory.GENBANK_FORMAT); + } + }); + + write_all_bases_menu.add (write_genbank_item); + + add (write_all_bases_menu); + + addSeparator (); + + final JMenuItem codon_usage_item = + new JMenuItem ("Write Codon Usage of Selected Features"); + codon_usage_item.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent event) { + writeCodonUsage (); + } + }); + + add (codon_usage_item); + } + + /** + * Create a new WriteMenu component. + * @param frame The JFrame that owns this JMenu. + * @param selection The Selection that the commands in the menu will + * operate on. + * @param entry_group The EntryGroup object to use when writing a sequence. + **/ + public WriteMenu (final JFrame frame, + final Selection selection, + final EntryGroup entry_group) { + this (frame, selection, entry_group, "Write"); + } + + /** + * Write a PIR database of the selected features to a file choosen by the + * user. + **/ + private void writePIRDataBase () { + if (! checkForSelectionFeatures ()) { + return; + } + + final File write_file = + getWriteFile ("Select a PIR output file name ...", "cosmid.pir"); + + if (write_file == null) { + return; + } + + try { + final FileWriter writer = new FileWriter (write_file); + + final FeatureVector features_to_write = + getSelection ().getAllFeatures (); + + for (int i = 0 ; i < features_to_write.size () ; ++i) { + final Feature selection_feature = features_to_write.elementAt (i); + + selection_feature.writePIROfFeature (writer); + } + + writer.close (); + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while writing: " + e.getMessage ()); + } + } + + /** + * Write the amino acid symbols of the selected features to a file choosen + * by the user. + **/ + private void writeAminoAcids () { + if (! checkForSelectionFeatures ()) { + return; + } + + final File write_file = + getWriteFile ("Select an output file name..", "amino_acids"); + + if (write_file == null) { + return; + } + + try { + final FileWriter writer = new FileWriter (write_file); + + final FeatureVector features_to_write = + getSelection ().getAllFeatures (); + + for (int i = 0 ; i < features_to_write.size () ; ++i) { + final Feature selection_feature = features_to_write.elementAt (i); + + selection_feature.writeAminoAcidsOfFeature (writer); + } + + writer.close (); + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while writing: " + e.getMessage ()); + } + } + + /** + * Write the bases of the selection to a file choosen by the user. + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private void writeBasesOfSelection (final int output_type) { + final MarkerRange marker_range = getSelection ().getMarkerRange (); + + if (marker_range == null) { + writeFeatureBases (output_type); + } else { + final String selection_bases = Strand.markerRangeBases (marker_range); + + writeBases (selection_bases, + "selected bases", + getSequenceFileName (output_type), + output_type); + } + } + + /** + * Write the bases of the selected features to a file choosen by the user + * or show a message and return immediately if there are no selected + * features. + * @param sequence The sequence to be written. + * @param header The header line that will be used on those output formats + * that need it. + * @param default_output_filename The filename that is passed to + * getWriteFile () + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private void writeBases (final String sequence, + final String header, + final String default_output_filename, + final int output_type) { + final File write_file = + getWriteFile ("Select an output file name ...", default_output_filename); + + if (write_file == null) { + return; + } + + try { + final FileWriter writer = new FileWriter (write_file); + + final StreamSequence stream_sequence; + + switch (output_type) { + case StreamSequenceFactory.FASTA_FORMAT: + stream_sequence = + new FastaStreamSequence (sequence, header); + break; + + case StreamSequenceFactory.EMBL_FORMAT: + stream_sequence = + new EmblStreamSequence (sequence); + break; + + case StreamSequenceFactory.GENBANK_FORMAT: + stream_sequence = + new GenbankStreamSequence (sequence); + break; + + case StreamSequenceFactory.RAW_FORMAT: + default: + stream_sequence = + new RawStreamSequence (sequence); + break; + } + + stream_sequence.writeToStream (writer); + + writer.close (); + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while writing: " + e.getMessage ()); + } + } + + /** + * Write the bases of the selected features to a file choosen by the user + * or show a message and return immediately if there are no selected + * features. + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private void writeFeatureBases (final int output_type) { + if (! checkForSelectionFeatures ()) { + return; + } + + final File write_file = + getWriteFile ("Select an output file name ...", "bases"); + + if (write_file == null) { + return; + } + + try { + final FileWriter writer = new FileWriter (write_file); + + final FeatureVector features_to_write = + getSelection ().getAllFeatures (); + + for (int i = 0 ; i < features_to_write.size () ; ++i) { + final Feature selection_feature = features_to_write.elementAt (i); + + final StringBuffer header_buffer = new StringBuffer (); + + header_buffer.append (selection_feature.getSystematicName ()); + header_buffer.append (" "); + header_buffer.append (selection_feature.getIDString ()); + header_buffer.append (" "); + + final String product = selection_feature.getProductString (); + + if (product == null) { + header_buffer.append ("undefined product"); + } else { + header_buffer.append (product); + } + + header_buffer.append (" ").append (selection_feature.getWriteRange ()); + + final StreamSequence stream_sequence = + getStreamSequence (selection_feature.getBases (), + header_buffer.toString (), + output_type); + + stream_sequence.writeToStream (writer); + } + + writer.close (); + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while writing: " + e.getMessage ()); + } + } + + /** + * Return a StreamSequence object for the given sequence string and the + * given type. + * @param sequence A String containing the sequence. + * @param header The header line that will be used on those output formats + * that need it. + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private StreamSequence getStreamSequence (final String sequence, + final String header, + final int output_type) { + + switch (output_type) { + case StreamSequenceFactory.FASTA_FORMAT: + return new FastaStreamSequence (sequence, header); + + case StreamSequenceFactory.EMBL_FORMAT: + return new EmblStreamSequence (sequence); + + case StreamSequenceFactory.GENBANK_FORMAT: + return new GenbankStreamSequence (sequence); + + case StreamSequenceFactory.RAW_FORMAT: + default: + return new RawStreamSequence (sequence); + } + } + + /** + * Write the upstream bases of the selected features to a file choosen by + * the user. The user can also choose the number of upstream base to + * write. + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private void writeUpstreamBases (final int output_type) { + if (! checkForSelectionFeatures ()) { + return; + } + + final TextRequester text_requester = + new TextRequester ("write how many bases upstream of each feature?", + 18, ""); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + if (event.getType () == TextRequesterEvent.CANCEL) { + return; + } + + final String base_count_string = event.getRequesterText ().trim (); + + if (base_count_string.length () == 0) { + new MessageDialog (getParentFrame (), "no bases written"); + return; + } + + try { + final int base_count = + Integer.valueOf (base_count_string).intValue (); + + + final File write_file = + getWriteFile ("Select an output file name ...", + "upstream_" + getSequenceFileName (output_type)); + + if (write_file == null) { + return; + } + + try { + final FileWriter writer = new FileWriter (write_file); + + final FeatureVector features_to_write = + getSelection ().getAllFeatures (); + + for (int i = 0 ; i < features_to_write.size () ; ++i) { + final Feature selection_feature = + features_to_write.elementAt (i); + + final String sequence_string = + selection_feature.getUpstreamBases (base_count); + + + final String header_line = + selection_feature.getIDString () + " - " + + sequence_string.length () + " bases upstream"; + + final StreamSequence stream_sequence = + getStreamSequence (sequence_string, + header_line, + output_type); + + stream_sequence.writeToStream (writer); + } + + writer.close (); + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while writing: " + e.getMessage ()); + } + } catch (NumberFormatException e) { + new MessageDialog (getParentFrame (), + "this is not a number: " + base_count_string); + } + } + }); + + text_requester.show (); + } + + /** + * Write the downstream bases of the selected features to a file choosen by + * the user. The user can also choose the number of downstream base to + * write. + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private void writeDownstreamBases (final int output_type) { + if (! checkForSelectionFeatures ()) { + return; + } + + final TextRequester text_requester = + new TextRequester ("write how many bases downstream of each feature?", + 18, ""); + + text_requester.addTextRequesterListener (new TextRequesterListener () { + public void actionPerformed (final TextRequesterEvent event) { + if (event.getType () == TextRequesterEvent.CANCEL) { + return; + } + + final String base_count_string = event.getRequesterText ().trim (); + + if (base_count_string.length () == 0) { + new MessageDialog (getParentFrame (), "no bases written"); + return; + } + + try { + final int base_count = + Integer.valueOf (base_count_string).intValue (); + + final File write_file = + getWriteFile ("Select an output file name ...", + "downstream_" + getSequenceFileName (output_type)); + + if (write_file == null) { + return; + } + + try { + final FileWriter writer = new FileWriter (write_file); + + final FeatureVector features_to_write = + getSelection ().getAllFeatures (); + + for (int i = 0 ; i < features_to_write.size () ; ++i) { + final Feature selection_feature = + features_to_write.elementAt (i); + + final String sequence_string = + selection_feature.getDownstreamBases (base_count); + + + final String header_line = + selection_feature.getIDString () + " - " + + sequence_string.length () + " bases downstream "; + + final StreamSequence stream_sequence = + getStreamSequence (sequence_string, + header_line, + output_type); + + stream_sequence.writeToStream (writer); + } + + writer.close (); + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while writing: " + e.getMessage ()); + } + } catch (NumberFormatException e) { + new MessageDialog (getParentFrame (), + "this is not a number: " + base_count_string); + } + } + }); + + text_requester.show (); + } + + /** + * Helper method for writeFeatureBases (), writeUpstreamBases () and + * writeDownstreamBases (). + * @return A string of the form "100:200 reverse" or "1:2222 forward". + **/ + private static String getWriteRange (final Feature feature) { + return (feature.isForwardFeature () ? + feature.getFirstCodingBaseMarker ().getRawPosition () + ":" + + feature.getLastBaseMarker ().getRawPosition () + + " forward" : + feature.getLastBaseMarker ().getRawPosition () + ":" + + feature.getFirstCodingBaseMarker ().getRawPosition () + + " reverse"); + } + + + /** + * Return a sensible file name to write sequence of the given type to. + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private String getSequenceFileName (final int output_type) { + switch (output_type) { + case StreamSequenceFactory.FASTA_FORMAT: + return "sequence.dna"; + + case StreamSequenceFactory.EMBL_FORMAT: + return "sequence.embl"; + + case StreamSequenceFactory.GENBANK_FORMAT: + return "sequence.genbank"; + + case StreamSequenceFactory.RAW_FORMAT: + default: + return "sequence.seq"; + } + } + + /** + * Write the bases from entry_group as raw sequence. + * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc. + **/ + private void writeAllSequence (final int output_type) { + final String file_name = getSequenceFileName (output_type); + + writeBases (entry_group.getBases ().toString (), + "all_bases", file_name, output_type); + } + + /** + * Popup a requester and ask for a file name to write to. If the file + * exists ask the user whether to overwrite. + * @param title The title of the new FileDialog JFrame. + * @param default_name The name to put in the FileDialog as a default. + **/ + private File getWriteFile (final String title, final String default_name) { + final JFileChooser dialog = new StickyFileChooser (); + + dialog.setDialogTitle (title); + + dialog.setFileSelectionMode (JFileChooser.FILES_ONLY); + + dialog.setSelectedFile (new File (default_name)); + dialog.setDialogType (JFileChooser.SAVE_DIALOG); + final int status = dialog.showSaveDialog (getParentFrame ()); + + if (status != JFileChooser.APPROVE_OPTION) { + return null; + } + + if (dialog.getSelectedFile () == null) { + return null; + } else { + final File write_file = dialog.getSelectedFile (); + + if (write_file.exists ()) { + final YesNoDialog yes_no_dialog = + new YesNoDialog (getParentFrame (), + "this file exists: " + write_file + + " overwrite it?"); + + if (yes_no_dialog.getResult ()) { + // yes - continue + } else { + // no + return null; + } + } + + return write_file; + } + } + + /** + * Write a table of codon usage for the selected features to a file choosen + * by the user. + **/ + private void writeCodonUsage () { + if (! checkForSelectionFeatures ()) { + return; + } + + final File write_file = + getWriteFile ("Select a codon usage output file ...", "usage"); + + if (write_file == null) { + return; + } + + try { + final PrintWriter writer = new PrintWriter (new FileWriter (write_file)); + + final int [] [] [] codon_counts = new int [4][4][4]; + + final FeatureVector features_to_write = + getSelection ().getAllFeatures (); + + int codon_total = 0; + + for (int i = 0 ; i < features_to_write.size () ; ++i) { + final Feature selection_feature = features_to_write.elementAt (i); + + for (int base1 = 0 ; base1 < 4 ; ++base1) { + for (int base2 = 0 ; base2 < 4 ; ++base2) { + for (int base3 = 0 ; base3 < 4 ; ++base3) { + codon_counts[base1][base2][base3] += + selection_feature.getCodonCount (base1, base2, base3); + } + } + } + + codon_total += selection_feature.getTranslationBasesLength () / 3; + } + + for (int base1 = 0 ; base1 < 4 ; ++base1) { + for (int base3 = 0 ; base3 < 4 ; ++base3) { + final StringBuffer buffer = new StringBuffer (); + + for (int base2 = 0 ; base2 < 4 ; ++base2) { + buffer.append (Bases.letter_index[base1]); + buffer.append (Bases.letter_index[base2]); + buffer.append (Bases.letter_index[base3]); + buffer.append (' '); + + final float per_thousand; + + if (codon_total > 0) { + per_thousand = + 10000 * codon_counts[base1][base2][base3] / codon_total * + 1.0F / 10; + } else { + per_thousand = 0.0F; + } + + buffer.append (per_thousand); + buffer.append ("( ").append (codon_counts[base1][base2][base3]); + buffer.append (") "); + } + + writer.println (buffer.toString ()); + } + } + + writer.close (); + } catch (IOException e) { + new MessageDialog (getParentFrame (), + "error while writing: " + e.getMessage ()); + } + } + + /** + * The EntryGroup object that was passed to the constructor. + **/ + private EntryGroup entry_group; +} diff --git a/uk/ac/sanger/artemis/components/YesNoDialog.java b/uk/ac/sanger/artemis/components/YesNoDialog.java new file mode 100644 index 000000000..6b7386c88 --- /dev/null +++ b/uk/ac/sanger/artemis/components/YesNoDialog.java @@ -0,0 +1,115 @@ +/* YesNoDialog.java + * + * created: Sat Dec 12 1998 + * + * This file is part of Artemis + * + * Copyright (C) 1998,1999,2000 Genome Research Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/YesNoDialog.java,v 1.1 2004-06-09 09:48:01 tjc Exp $ + */ + +package uk.ac.sanger.artemis.components; + +import uk.ac.sanger.artemis.Options; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * A popup dialog box that displays a message and then waits for the to press + * yes or no. + * + * @author Kim Rutherford + * @version $Id: YesNoDialog.java,v 1.1 2004-06-09 09:48:01 tjc Exp $ + **/ + +public class YesNoDialog extends JDialog { + /** + * Create a new YesNoDialog component. The constructor does not show () + * the dialog, call getResult () to do that. + * @param parent The parent window. + * @param title The title of the new dialog JFrame. + * @param message The message to display in the JDialog. + **/ + public YesNoDialog (JFrame parent, String title, String message) { + super (parent, message, true); + + getContentPane ().add (new JLabel (message), "North"); + + final JPanel panel = new JPanel (); + + panel.add (yes_button); + yes_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + button_result = true; + setVisible (false); + YesNoDialog.this.dispose (); + } + }); + + panel.add (no_button); + no_button.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + button_result = false; + setVisible (false); + YesNoDialog.this.dispose (); + } + }); + + getContentPane ().add (panel, "South"); + pack (); + + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation (new Point ((screen.width - getSize ().width) / 2, + (screen.height - getSize ().height) / 2)); + } + + /** + * Create a new YesNoDialog component. The constructor does not show () + * the dialog, call getResult () to do that. + * @param parent The parent window. + * @param message The message to display in the JDialog and to uise as the + * title string. + **/ + public YesNoDialog (JFrame parent, String message) { + this (parent, message, message); + } + + /** + * This method calls show () on this object, and then waits for the user to + * press the Yes button or the No button. + * @return true is the user pressed Yes, false otherwise. + **/ + public boolean getResult () { + setVisible (true); + + return button_result; + } + + final JButton yes_button = new JButton ("Yes"); + + final JButton no_button = new JButton ("No"); + + /** + * Set by the yes and no button action listeners and read by getResult (). + **/ + boolean button_result; +} + -- GitLab