From: gmungoc Date: Wed, 29 Mar 2017 15:36:03 +0000 (+0100) Subject: Merge branch 'develop' into bug/JAL-1608createGroups X-Git-Tag: Release_2_10_3b1~336^2~2^2~3 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=c437fc72420baf7b045d417fb6cabcebe87987c5;hp=ede54837349adde8f7265c6d8fa58d14165ee215;p=jalview.git Merge branch 'develop' into bug/JAL-1608createGroups --- diff --git a/examples/exampleFeatures.txt b/examples/exampleFeatures.txt index 2de9817..83dc4b1 100755 --- a/examples/exampleFeatures.txt +++ b/examples/exampleFeatures.txt @@ -1,5 +1,5 @@ ST-TURN-IIL blue|255,0,255|absolute|20.0|95.0|below|66.0 -GAMMA-TURN-CLASSIC red|0,255,255|20.0|95.0|below|66.0 +GAMMA-TURN-CLASSIC lightGray|0,255,255|20.0|95.0|below|66.0 BETA-TURN-IR 9a6a94 BETA-TURN-IL d6a6ca BETA-BULGE 1dc451 diff --git a/help/help.jhm b/help/help.jhm index f69ed00..984c2d1 100755 --- a/help/help.jhm +++ b/help/help.jhm @@ -150,6 +150,9 @@ + + + diff --git a/help/helpTOC.xml b/help/helpTOC.xml index 54abd53..482ccdf 100755 --- a/help/helpTOC.xml +++ b/help/helpTOC.xml @@ -131,6 +131,9 @@ + + + diff --git a/help/html/features/preferences.html b/help/html/features/preferences.html index 6a8c86c..da045ba 100755 --- a/help/html/features/preferences.html +++ b/help/html/features/preferences.html @@ -46,8 +46,13 @@ and displaying structure information.
  • The "Connections" - Preferences tab allows you to change the links made from Jalview - to your default web browser. + Preferences tab allows you to configure Jalview's internet + settings and specify your default web browser. +
  • +
  • The "Links" + Preferences tab shows the currently configured URL + Links shown in the Link submenu in the Sequence + ID popup menu.
  • The "Output" Preferences tab contains settings affecting the export of @@ -212,13 +217,6 @@ Preferences tab

    - URL Link From Sequence ID
    These definitions are - used to generate URLs from a sequence's ID or database cross - references. Read more about configuring - URL links here. -

    -

    Default Browser (Unix)
    Its difficult in Java to detect the default web browser for Unix users. If Jalview can't find your default web browser, enter the name or full path to your web @@ -240,6 +238,33 @@ statement for more information.

    + The "Links" Preferences + tab +

    +

    + This panel shows a table, and two sections - Edit and Filter. + The table shows the available URL link definitions (consisting of a + database, Name, and URL template string), a checkbox In + Menu which indicates if the link is enabled, and Double + Click which marks the link that will be opened if a sequence's ID + is double clicked. The table can be sorted by clicking on the column headers. +

    +

    Edit Links
    This section contains three buttons, + New, Edit and Delete, which allow you to + create, modify and remove user-defined URL links from the Sequence + ID's links submenu. +

    +

    + Filter
    The Filter text box allows you to + quickly show rows in the table containing a particular text string. + The Custom only button limits the entries in the table to + just those you have configured yourself via the Edit + Links buttons. Press Show all to clear any filters. +

    + Read more about configuring + URL links. +

    +

    Output Preferences tab

    @@ -307,7 +332,7 @@ and PDB file association (if available). The Jalview id/start-end option is ignored if Modeller output is selected.

    - Editing Preferences tab + e"Editinge" Preferences tab

    There are currently three options available which can be selected / deselected.

    diff --git a/help/html/webServices/urllinks.html b/help/html/webServices/urllinks.html index 088a539..da5d7dd 100644 --- a/help/html/webServices/urllinks.html +++ b/help/html/webServices/urllinks.html @@ -23,42 +23,55 @@

    -

    Opening URLs from Jalview
    Both the applet and the desktop application are able to open URLs as 'popups' in - your web browser.
    Double-clicking on the ID of a sequence - will open the first URL that can be generated from its sequence ID. + your web browser.

    +

    Double-clicking on the ID of a sequence + will open whichever URL is selected for 'popups' in the "Links" tab of the Jalview desktop + preferences. This is by default the EMBL-EBI site, but you can easily configure your own sequence URL links.

    - Other links for a sequence either derived from any other configured + Other links for a sequence, either derived from any other configured URL links, or imported from the sequence's annotation, are accessed by right clicking to open the sequence pop-up menu, and selecting from the Links submenu.

    Configuring URL Links
    URL - links are defined in the "Connections" tab of the Jalview desktop + links are defined in the "Links" tab of the Jalview desktop preferences, or specified as applet - parameters.
    By default the item "EMBL-EBI Search" is added - to this link menu. This link will show a web page in your default - browser with the selected sequence id as part of the URL.
    - In the preferences dialog box, click new to add a - new link, and edit to modify an existing link, or delete - to remove it.
    You can name the link, this will be displayed - on a new menu item under the "Link" menu when you right - click on a sequence id.
    The URL string must contain a - token that can be replaced with a sequence ID or DB accession ID. The simplest token is - "$SEQUENCE_ID$", which will be replaced by the chosen - sequence id when you click on it. + parameters.

    +

    + Default Link Settings
    The "EMBL-EBI Search" + link is the default link shown in the "Link" submenu, and + opened when double-clicking on a sequence ID. When clicked, this + link will show a web page in your default browser with the selected + sequence ID as part of the URL. +

    +

    + Adding additional links
    You can configure your own + links via the Jalview Preferences + dialog. Jalview also provides persistent URLs for many common + bioinformatics databases. These links are downloaded by Jalview from + the identifiers.org website, and the names and URLs are not + user editable.

    - eg.
    UniRef100 = - http://www.ebi.uniprot.org/uniprot-srv/uniRefView.do?proteinAc=$SEQUENCE_ID$&library=uniref100
    - Swissprot = http://www.expasy.org/uniprot/$SEQUENCE_ID$

    + Creating your own URL link URL links are specified as a + template containing special tokens that Jalview will replace with + the Sequence ID or Database Accession of the sequence when you + double click on its ID or open it's Link submenu. + Link URL templates must contain at least one token. +

    + eg.
     UniRef100 =
    +    http://www.ebi.uniprot.org/uniprot-srv/uniRefView.do?proteinAc=$SEQUENCE_ID$&library=uniref100
    + Swissprot = http://www.expasy.org/uniprot/$SEQUENCE_ID$
    +

    Links will also be made for any database cross references associated with the sequence where the database name exactly matches a URL link name. In this case, the $DB_ACCESSION$ string will be replaced with @@ -83,33 +96,29 @@

    Regular Expression Substitution
    A url may contain a string of the form $SEQUENCE_ID=/regular - expression/=$ or $DB_ACCESSION=/regular expression/=$. - In this case, the regular expression will be - applied to the full sequence ID or DB accession ID string and the resulting match will + expression/=$ or $DB_ACCESSION=/regular expression/=$. In + this case, the regular expression will be applied to the full + sequence ID or DB accession ID string and the resulting match will be inserted into the URL. Groups of parentheses can be used to specify which regions of the regular expression will be used to generate the URL: +

    • Each top level parenthesis will yield a URL containing the text matched within that parenthesis.
    • Regions matching sub-parentheses within a top-level parenthesis will be concatenated to form the text inserted into the URL for the top-level parenthesis.
    • - Please Note: -
        -
      • The regular expressions supported by Jalview are those - provided by the Stevesoft - javaregex package. -
      • -
      • Some characters must be escaped when specifying them as - a match within a regular expression.
      • -

      Many Thanks to Bernd Brandt of the Free University of - Amsterdam for testing this new regular-expression expansion - feature! -
      -
    -

    -

    + Please Note: +
      +
    • The regular expressions supported by Jalview are those + provided by the Stevesoft + javaregex package. +
    • +
    • Some characters must be escaped when specifying them as a + match within a regular expression.
    • +

    Many Thanks to Bernd Brandt of the Free University of + Amsterdam for testing the regular-expression expansion feature! diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 56565ad..d567699 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -137,7 +137,8 @@ action.view_flanking_regions = Show flanking regions label.view_flanking_regions = Show sequence data either side of the subsequences involved in this alignment label.structures_manager = Structures Manager label.nickname = Nickname: -label.url = URL: +label.url = URL +label.url\: = URL: label.input_file_url = Enter URL or Input File label.select_feature = Select feature label.name = Name @@ -412,7 +413,6 @@ label.couldnt_import_as_vamsas_session = Couldn't import {0} as a new vamsas ses label.vamsas_document_import_failed = Vamsas Document Import Failed label.couldnt_locate = Couldn't locate {0} label.url_not_found = URL not found -label.no_link_selected = No link selected label.new_sequence_url_link = New sequence URL link label.cannot_edit_annotations_in_wrapped_view = Cannot edit annotations in wrapped view label.wrapped_view_no_edit = Wrapped view - no edit @@ -514,7 +514,7 @@ label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences = label.standard_databases = Standard Databases label.fetch_embl_uniprot = Fetch from EMBL/EMBLCDS or Uniprot/PDB and any selected DAS sources label.reset_min_max_colours_to_defaults = Reset min and max colours to defaults from user preferences. -label.align_structures_using_linked_alignment_views = Align structures using {0} linked alignment views +label.align_structures_using_linked_alignment_views = Superpose structures using {0} selected alignment view(s) label.connect_to_session = Connect to session {0} label.threshold_feature_display_by_score = Threshold the feature display by score. label.threshold_feature_no_threshold = No Threshold @@ -619,6 +619,8 @@ label.web_services = Web Services label.right_click_to_edit_currently_selected_parameter = Right click to edit currently selected parameter. label.let_jmol_manage_structure_colours = Let Jmol manage structure colours label.let_chimera_manage_structure_colours = Let Chimera manage structure colours +label.fetch_chimera_attributes = Fetch Chimera attributes +label.fetch_chimera_attributes_tip = Copy Chimera attribute to Jalview feature label.marks_leaves_tree_not_associated_with_sequence = Marks leaves of tree not associated with a sequence label.index_web_services_menu_by_host_site = Index web services in menu by the host site label.option_want_informed_web_service_URL_cannot_be_accessed_jalview_when_starts_up = Check this option if you want to be informed
    when a web service URL cannot be accessed by Jalview
    when it starts up @@ -712,9 +714,14 @@ label.link_name = Link Name label.pdb_file = PDB file label.colour_with_jmol = Colour with Jmol label.colour_with_chimera = Colour with Chimera -label.align_structures = Align Structures +label.superpose_structures = Superpose Structures +error.superposition_failed = Superposition failed: {0} +label.insufficient_residues = Not enough aligned residues ({0}) to perform superposition label.jmol = Jmol label.chimera = Chimera +label.create_chimera_attributes = Write Jalview features +label.create_chimera_attributes_tip = Set Chimera residue attributes for visible features +label.attributes_set = {0} attribute values set on Chimera label.sort_alignment_by_tree = Sort Alignment By Tree label.mark_unlinked_leaves = Mark Unlinked Leaves label.associate_leaves_with = Associate Leaves With @@ -1270,4 +1277,21 @@ label.SEQUENCE_ID_no_longer_used = $SEQUENCE_ID$ is no longer used for DB access label.SEQUENCE_ID_for_DB_ACCESSION1 = Please review your URL links in the 'Connections' tab of the Preferences window: label.SEQUENCE_ID_for_DB_ACCESSION2 = URL links using '$SEQUENCE_ID$' for DB accessions now use '$DB_ACCESSION$'. label.do_not_display_again = Do not display this message again +exception.url_cannot_have_miriam_id = {0} is a MIRIAM id and cannot be used as a custom url name +exception.url_cannot_have_duplicate_id = {0} cannot be used as a label for more than one line +label.filter = Filter text: +action.customfilter = Custom only +action.showall = Show All +label.insert = Insert: +action.seq_id = $SEQUENCE_ID$ +action.db_acc = $DB_ACCESSION$ +label.primary = Double Click +label.inmenu = In Menu +label.id = ID +label.database = Database +label.urltooltip = Only one url, which must use a sequence id, can be selected for the 'On Click' option +label.edit_sequence_url_link = Edit sequence URL link +warn.name_cannot_be_duplicate = User-defined URL names must be unique and cannot be MIRIAM ids +label.invalid_name = Invalid Name ! label.output_seq_details = Output Sequence Details to list all database references +label.urllinks = Links diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index a4180dc..b8cf4f9 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -134,7 +134,8 @@ action.view_flanking_regions = Mostrar flancos label.view_flanking_regions = Mostrar los datos de la secuencia a ambos lados de las subsecuencias implicadas en este alineamiento label.structures_manager = Administrar estructuras label.nickname = Sobrenombre: -label.url = URL: +label.url\: = URL: +label.url = URL label.input_file_url = Introducir URL en el fichero de entrada label.select_feature = Seleccionar característica label.name = Nombre @@ -379,7 +380,6 @@ label.couldnt_import_as_vamsas_session = No se pudo importar {0} como una nueva label.vamsas_document_import_failed = Fallo en la importación del documento Vamsas label.couldnt_locate = No se pudo localizar {0} label.url_not_found = URL no encontrada -label.no_link_selected = Enlace no seleccionado label.new_sequence_url_link = Enlace a una nueva secuencia URL label.cannot_edit_annotations_in_wrapped_view = No se pueden editar anotaciones en vista envolvente label.wrapped_view_no_edit = Vista envolvente - no editar @@ -475,7 +475,7 @@ label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences = label.standard_databases = Bases de datos estándar label.fetch_embl_uniprot = Recuperar de EMBL/EMBLCDS o Uniprot/PDB y de cualquier fuente DAS seleccionada label.reset_min_max_colours_to_defaults = Reiniciar los colores min y max colours a los valores por defecto establecidos en las preferencias de usuario -label.align_structures_using_linked_alignment_views = Alinear las estructuras utlizando las {0} vistas de alineamiento enlazadas +label.align_structures_using_linked_alignment_views = Alinear las estructuras utilizando las {0} vista(s) de alineamiento enlazada(s) label.connect_to_session = Conectar a la sesión {0} label.threshold_feature_display_by_score = Filtrar la característica mostrada por puntuación. label.threshold_feature_no_threshold = Sin umbral @@ -1270,4 +1270,21 @@ label.SEQUENCE_ID_no_longer_used = $SEQUENCE_ID$ no se utiliza m label.SEQUENCE_ID_for_DB_ACCESSION1 = Por favor, revise sus URLs en la pestaña 'Conexiones' de la ventana de Preferencias: label.SEQUENCE_ID_for_DB_ACCESSION2 = URL enlaza usando '$SEQUENCE_ID$' para accesiones DB ahora usar '$DB_ACCESSION$'. label.do_not_display_again = No mostrar este mensaje de nuevo +exception.url_cannot_have_miriam_id = {0} es una id MIRIAM y no puede ser usada como nombre url personalizado +exception.url_cannot_have_duplicate_id = {0} no puede ser usada como etiqueta en más de un enlace +label.filter = Filtrar texto: +action.customfilter = Sólo personalizado +action.showall = Mostrar todo +label.insert = Insertar: +action.seq_id = $SEQUENCE_ID$ +action.db_acc = $DB_ACCESSION$ +label.primary = Doble clic +label.inmenu = En Menú +label.id = ID +label.database = Base de datos +label.urltooltip = Sólo una url, que debe usar una id de secuencia, puede ser seleccionada en la opción 'On Click' +label.edit_sequence_url_link = Editar link de secuencia URL +warn.name_cannot_be_duplicate = Los nombres URL definidos por el usuario deben ser únicos y no pueden ser ids de MIRIAM +label.invalid_name = Nombre inválido ! label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas +label.urllinks = Enlaces diff --git a/src/MCview/AppletPDBCanvas.java b/src/MCview/AppletPDBCanvas.java index aac796c..3ae0650 100644 --- a/src/MCview/AppletPDBCanvas.java +++ b/src/MCview/AppletPDBCanvas.java @@ -28,6 +28,7 @@ import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.io.DataSourceType; import jalview.io.StructureFile; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.AtomSpec; import jalview.structure.StructureListener; import jalview.structure.StructureMapping; @@ -577,6 +578,8 @@ public class AppletPDBCanvas extends Panel implements MouseListener, showFeatures = true; } + FeatureColourFinder finder = new FeatureColourFinder(fr); + PDBChain chain; if (bysequence && pdb != null) { @@ -604,25 +607,16 @@ public class AppletPDBCanvas extends Panel implements MouseListener, if (pos > 0) { pos = sequence[s].findIndex(pos); - tmp.startCol = sr.getResidueBoxColour(sequence[s], pos); - if (showFeatures) - { - tmp.startCol = fr.findFeatureColour(tmp.startCol, - sequence[s], pos); - } + tmp.startCol = sr.getResidueColour(sequence[s], pos, + finder); } pos = mapping[m].getSeqPos(tmp.at2.resNumber) - 1; if (pos > 0) { pos = sequence[s].findIndex(pos); - tmp.endCol = sr.getResidueBoxColour(sequence[s], pos); - if (showFeatures) - { - tmp.endCol = fr.findFeatureColour(tmp.endCol, - sequence[s], pos); - } + tmp.endCol = sr + .getResidueColour(sequence[s], pos, finder); } - } } } diff --git a/src/MCview/PDBCanvas.java b/src/MCview/PDBCanvas.java index 292de91..08bca8c 100644 --- a/src/MCview/PDBCanvas.java +++ b/src/MCview/PDBCanvas.java @@ -28,6 +28,7 @@ import jalview.gui.FeatureRenderer; import jalview.gui.SequenceRenderer; import jalview.io.DataSourceType; import jalview.io.StructureFile; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.AtomSpec; import jalview.structure.StructureListener; import jalview.structure.StructureMapping; @@ -546,6 +547,7 @@ public class PDBCanvas extends JPanel implements MouseListener, showFeatures = true; } + FeatureColourFinder finder = new FeatureColourFinder(fr); PDBChain chain; if (bysequence && pdb != null) { @@ -573,23 +575,15 @@ public class PDBCanvas extends JPanel implements MouseListener, if (pos > 0) { pos = sequence[s].findIndex(pos); - tmp.startCol = sr.getResidueBoxColour(sequence[s], pos); - if (showFeatures) - { - tmp.startCol = fr.findFeatureColour(tmp.startCol, - sequence[s], pos); - } + tmp.startCol = sr.getResidueColour(sequence[s], pos, + finder); } pos = mapping[m].getSeqPos(tmp.at2.resNumber) - 1; if (pos > 0) { pos = sequence[s].findIndex(pos); - tmp.endCol = sr.getResidueBoxColour(sequence[s], pos); - if (showFeatures) - { - tmp.endCol = fr.findFeatureColour(tmp.endCol, - sequence[s], pos); - } + tmp.endCol = sr + .getResidueColour(sequence[s], pos, finder); } } diff --git a/src/MCview/PDBChain.java b/src/MCview/PDBChain.java index c40cdda..ba93046 100755 --- a/src/MCview/PDBChain.java +++ b/src/MCview/PDBChain.java @@ -39,6 +39,8 @@ import java.util.Vector; public class PDBChain { + public static final String RESNUM_FEATURE = "RESNUM"; + /** * SequenceFeature group for PDB File features added to sequences */ @@ -367,7 +369,7 @@ public class PDBChain Residue tmpres = residues.lastElement(); Atom tmpat = tmpres.atoms.get(0); // Make A new SequenceFeature for the current residue numbering - SequenceFeature sf = new SequenceFeature("RESNUM", tmpat.resName + SequenceFeature sf = new SequenceFeature(RESNUM_FEATURE, tmpat.resName + ":" + tmpat.resNumIns + " " + pdbid + id, "", offset + count, offset + count, pdbid); resFeatures.addElement(sf); diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java index 439d479..85ae718 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java @@ -423,6 +423,11 @@ public class ChimeraManager "list selection level residue", true); if (chimeraReply != null) { + /* + * expect 0, 1 or more lines of the format + * residue id #0:43.A type GLY + * where we are only interested in the atomspec #0.43.A + */ for (String inputLine : chimeraReply) { String[] inputLineParts = inputLine.split("\\s+"); diff --git a/src/jalview/analysis/AAFrequency.java b/src/jalview/analysis/AAFrequency.java index 17874e6..ee16f94 100755 --- a/src/jalview/analysis/AAFrequency.java +++ b/src/jalview/analysis/AAFrequency.java @@ -20,6 +20,10 @@ */ package jalview.analysis; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + import jalview.datamodel.AlignedCodonFrame; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; @@ -37,10 +41,6 @@ import jalview.util.Format; import jalview.util.MappingUtils; import jalview.util.QuickSort; -import java.util.Arrays; -import java.util.Hashtable; -import java.util.List; - /** * Takes in a vector or array of sequences and column start and column end and * returns a new Hashtable[] of size maxSeqLength, if Hashtable not supplied. @@ -67,8 +67,8 @@ public class AAFrequency } } - public static final ProfilesI calculate(List list, - int start, int end) + public static final ProfilesI calculate(List list, int start, + int end) { return calculate(list, start, end, false); } @@ -289,6 +289,56 @@ public class AAFrequency } /** + * Derive the gap count annotation row. + * + * @param gaprow + * the annotation row to add annotations to + * @param profiles + * the source consensus data + * @param startCol + * start column (inclusive) + * @param endCol + * end column (exclusive) + */ + public static void completeGapAnnot(AlignmentAnnotation gaprow, + ProfilesI profiles, int startCol, int endCol, long nseq) + { + if (gaprow == null || gaprow.annotations == null + || gaprow.annotations.length < endCol) + { + /* + * called with a bad alignment annotation row + * wait for it to be initialised properly + */ + return; + } + // always set ranges again + gaprow.graphMax = nseq; + gaprow.graphMin = 0; + for (int i = startCol; i < endCol; i++) + { + ProfileI profile = profiles.get(i); + if (profile == null) + { + /* + * happens if sequences calculated over were + * shorter than alignment width + */ + gaprow.annotations[i] = null; + return; + } + + final int gapped = profile.getNonGapped(); + + String description = String.valueOf(gapped); + + gaprow.annotations[i] = new Annotation(description, description, + '\0', + gapped); + } + } + + /** * Returns a tooltip showing either *
      *
    • the full profile (percentages of all residues present), if @@ -357,8 +407,7 @@ public class AAFrequency * calculations * @return */ - public static int[] extractProfile(ProfileI profile, - boolean ignoreGaps) + public static int[] extractProfile(ProfileI profile, boolean ignoreGaps) { int[] rtnval = new int[64]; ResidueCount counts = profile.getCounts(); diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index ea96b3b..232cb5d 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -42,6 +42,7 @@ import jalview.util.Comparison; import jalview.util.DBRefUtils; import jalview.util.MapList; import jalview.util.MappingUtils; +import jalview.util.RangeComparator; import jalview.util.StringUtils; import java.io.UnsupportedEncodingException; @@ -2265,14 +2266,7 @@ public class AlignmentUtils * ranges are assembled in order. Other cases should not use this method, * but instead construct an explicit mapping for CDS (e.g. EMBL parsing). */ - Collections.sort(result, new Comparator() - { - @Override - public int compare(int[] o1, int[] o2) - { - return Integer.compare(o1[0], o2[0]); - } - }); + Collections.sort(result, new RangeComparator(true)); return result; } diff --git a/src/jalview/analysis/PCA.java b/src/jalview/analysis/PCA.java index eaea7bf..9babaee 100755 --- a/src/jalview/analysis/PCA.java +++ b/src/jalview/analysis/PCA.java @@ -20,9 +20,7 @@ */ package jalview.analysis; -import jalview.datamodel.BinarySequence; -import jalview.datamodel.BinarySequence.InvalidSequenceTypeException; -import jalview.math.Matrix; +import jalview.math.MatrixI; import jalview.schemes.ResidueProperties; import jalview.schemes.ScoreMatrix; @@ -30,23 +28,22 @@ import java.io.PrintStream; /** * Performs Principal Component Analysis on given sequences - * - * @author $author$ - * @version $Revision$ */ public class PCA implements Runnable { - Matrix m; - - Matrix symm; + boolean jvCalcMode = true; - Matrix m2; + MatrixI symm; double[] eigenvalue; - Matrix eigenvector; + MatrixI eigenvector; - StringBuffer details = new StringBuffer(); + StringBuilder details = new StringBuilder(1024); + + private String[] seqs; + + private ScoreMatrix scoreMatrix; /** * Creates a new PCA object. By default, uses blosum62 matrix to generate @@ -77,87 +74,23 @@ public class PCA implements Runnable public PCA(String[] s, boolean nucleotides, String s_m) { + this.seqs = s; - BinarySequence[] bs = new BinarySequence[s.length]; - int ii = 0; - - while ((ii < s.length) && (s[ii] != null)) - { - bs[ii] = new BinarySequence(s[ii], nucleotides); - bs[ii].encode(); - ii++; - } - - BinarySequence[] bs2 = new BinarySequence[s.length]; - ii = 0; - ScoreMatrix smtrx = null; + scoreMatrix = null; String sm = s_m; if (sm != null) { - smtrx = ResidueProperties.getScoreMatrix(sm); + scoreMatrix = ResidueProperties.getScoreMatrix(sm); } - if (smtrx == null) + if (scoreMatrix == null) { // either we were given a non-existent score matrix or a scoremodel that // isn't based on a pairwise symbol score matrix - smtrx = ResidueProperties.getScoreMatrix(sm = (nucleotides ? "DNA" - : "BLOSUM62")); + scoreMatrix = ResidueProperties + .getScoreMatrix(sm = (nucleotides ? "DNA" : "BLOSUM62")); } details.append("PCA calculation using " + sm + " sequence similarity matrix\n========\n\n"); - while ((ii < s.length) && (s[ii] != null)) - { - bs2[ii] = new BinarySequence(s[ii], nucleotides); - if (smtrx != null) - { - try - { - bs2[ii].matrixEncode(smtrx); - } catch (InvalidSequenceTypeException x) - { - details.append("Unexpected mismatch of sequence type and score matrix. Calculation will not be valid!\n\n"); - } - } - ii++; - } - - // System.out.println("Created binary encoding"); - // printMemory(rt); - int count = 0; - - while ((count < bs.length) && (bs[count] != null)) - { - count++; - } - - double[][] seqmat = new double[count][bs[0].getDBinary().length]; - double[][] seqmat2 = new double[count][bs2[0].getDBinary().length]; - int i = 0; - - while (i < count) - { - seqmat[i] = bs[i].getDBinary(); - seqmat2[i] = bs2[i].getDBinary(); - i++; - } - - // System.out.println("Created array"); - // printMemory(rt); - // System.out.println(" --- Original matrix ---- "); - m = new Matrix(seqmat); - m2 = new Matrix(seqmat2); - - } - - /** - * Returns the matrix used in PCA calculation - * - * @return java.math.Matrix object - */ - - public Matrix getM() - { - return m; } /** @@ -170,7 +103,7 @@ public class PCA implements Runnable */ public double getEigenvalue(int i) { - return eigenvector.d[i]; + return eigenvector.getD()[i]; } /** @@ -189,9 +122,9 @@ public class PCA implements Runnable */ public float[][] getComponents(int l, int n, int mm, float factor) { - float[][] out = new float[m.rows][3]; + float[][] out = new float[getHeight()][3]; - for (int i = 0; i < m.rows; i++) + for (int i = 0; i < getHeight(); i++) { out[i][0] = (float) component(i, l) * factor; out[i][1] = (float) component(i, n) * factor; @@ -212,9 +145,9 @@ public class PCA implements Runnable public double[] component(int n) { // n = index of eigenvector - double[] out = new double[m.rows]; + double[] out = new double[getHeight()]; - for (int i = 0; i < m.rows; i++) + for (int i = 0; i < out.length; i++) { out[i] = component(i, n); } @@ -236,12 +169,12 @@ public class PCA implements Runnable { double out = 0.0; - for (int i = 0; i < symm.cols; i++) + for (int i = 0; i < symm.width(); i++) { - out += (symm.value[row][i] * eigenvector.value[i][n]); + out += (symm.getValue(row, i) * eigenvector.getValue(i, n)); } - return out / eigenvector.d[n]; + return out / eigenvector.getD()[n]; } public String getDetails() @@ -270,25 +203,17 @@ public class PCA implements Runnable } }; + // long now = System.currentTimeMillis(); try { details.append("PCA Calculation Mode is " + (jvCalcMode ? "Jalview variant" : "Original SeqSpace") + "\n"); - Matrix mt = m.transpose(); - details.append(" --- OrigT * Orig ---- \n"); - if (!jvCalcMode) - { - eigenvector = mt.preMultiply(m); // standard seqspace comparison matrix - } - else - { - eigenvector = mt.preMultiply(m2); // jalview variation on seqsmace - // method - } + eigenvector = scoreMatrix.computePairwiseScores(seqs); - eigenvector.print(ps); + details.append(" --- OrigT * Orig ---- \n"); + eigenvector.print(ps, "%8.2f"); symm = eigenvector.copy(); @@ -296,10 +221,10 @@ public class PCA implements Runnable details.append(" ---Tridiag transform matrix ---\n"); details.append(" --- D vector ---\n"); - eigenvector.printD(ps); + eigenvector.printD(ps, "%15.4e"); ps.println(); details.append("--- E vector ---\n"); - eigenvector.printE(ps); + eigenvector.printE(ps, "%15.4e"); ps.println(); // Now produce the diagonalization matrix @@ -313,9 +238,9 @@ public class PCA implements Runnable } details.append(" --- New diagonalization matrix ---\n"); - eigenvector.print(ps); + eigenvector.print(ps, "%8.2f"); details.append(" --- Eigenvalues ---\n"); - eigenvector.printD(ps); + eigenvector.printD(ps, "%15.4e"); ps.println(); /* * for (int seq=0;seq + * The graphics argument should be provided if transparency is applied + * (getTransparency() < 1). With feature transparency, visible features are + * written to the graphics context and the composite colour may be read off + * from it. In this case, the returned feature colour is not the composite + * colour but that of the last feature drawn. + *

      + * If no transparency applies, then the graphics argument may be null, and the + * returned colour is the one that would be drawn for the feature. + *

      + * Returns null if there is no visible feature at the position. + *

      + * This is provided to support rendering of feature colours other than on the + * sequence alignment, including by structure viewers and the overview window. + * Note this method takes no account of whether the sequence or column is + * hidden. * - * @param col - * - background colour (due to alignment/group shading schemes, etc). - * @param sequenceI - * - sequence providing features - * @param r - * - column position + * @param sequence + * @param column + * @param g * @return */ - Color findFeatureColour(Color col, SequenceI sequenceI, int r); + Color findFeatureColour(SequenceI sequence, int column, Graphics g); /** * trigger the feature discovery process for a newly created feature renderer. @@ -170,4 +185,19 @@ public interface FeatureRenderer */ void setVisible(String featureType); + /** + * Sets the transparency value, between 0 (full transparency) and 1 (no + * transparency) + * + * @param value + */ + void setTransparency(float value); + + /** + * Returns the transparency value, between 0 (full transparency) and 1 (no + * transparency) + * + * @return + */ + float getTransparency(); } diff --git a/src/jalview/api/SequenceRenderer.java b/src/jalview/api/SequenceRenderer.java index d708902..54f7fb6 100644 --- a/src/jalview/api/SequenceRenderer.java +++ b/src/jalview/api/SequenceRenderer.java @@ -21,14 +21,14 @@ package jalview.api; import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureColourFinder; import java.awt.Color; public interface SequenceRenderer { - Color getResidueBoxColour(SequenceI sequenceI, int r); - - Color getResidueColour(SequenceI seq, int position, FeatureRenderer fr); + Color getResidueColour(SequenceI seq, int position, + FeatureColourFinder finder); } diff --git a/src/jalview/appletgui/APopupMenu.java b/src/jalview/appletgui/APopupMenu.java index 055fcf3..8fd317a 100644 --- a/src/jalview/appletgui/APopupMenu.java +++ b/src/jalview/appletgui/APopupMenu.java @@ -210,7 +210,7 @@ public class APopupMenu extends java.awt.PopupMenu implements Menu menu1 = new Menu(); public APopupMenu(AlignmentPanel apanel, final SequenceI seq, - Vector links) + List links) { // ///////////////////////////////////////////////////////// // If this is activated from the sequence panel, the user may want to diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index 5adb71a..65c4fef 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -75,6 +75,7 @@ import jalview.structures.models.AAStructureBindingModel; import jalview.util.MappingUtils; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Canvas; @@ -421,6 +422,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, @Override public void keyPressed(KeyEvent evt) { + ViewportRanges ranges = viewport.getRanges(); + if (viewport.cursorMode && ((evt.getKeyCode() >= KeyEvent.VK_0 && evt.getKeyCode() <= KeyEvent.VK_9) || (evt .getKeyCode() >= KeyEvent.VK_NUMPAD0 && evt @@ -572,8 +575,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, new String[] { (viewport.cursorMode ? "on" : "off") })); if (viewport.cursorMode) { - alignPanel.seqPanel.seqCanvas.cursorX = viewport.startRes; - alignPanel.seqPanel.seqCanvas.cursorY = viewport.startSeq; + alignPanel.seqPanel.seqCanvas.cursorX = ranges.getStartRes(); + alignPanel.seqPanel.seqCanvas.cursorY = ranges.getStartSeq(); } break; @@ -599,8 +602,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, } else { - alignPanel.setScrollValues(viewport.startRes, viewport.startSeq - - viewport.endSeq + viewport.startSeq); + alignPanel.setScrollValues(ranges.getStartRes(), + 2 * ranges.getStartSeq() - ranges.getEndSeq()); } break; @@ -611,8 +614,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, } else { - alignPanel.setScrollValues(viewport.startRes, viewport.startSeq - + viewport.endSeq - viewport.startSeq); + alignPanel + .setScrollValues(ranges.getStartRes(), ranges.getEndSeq()); } break; @@ -2072,7 +2075,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, seqs, 0, viewport.getAlignment().getWidth(), viewport.getAlignment())); - viewport.setEndSeq(viewport.getAlignment().getHeight()); + viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight()); viewport.getAlignment().getWidth(); viewport.firePropertyChange("alignment", null, viewport.getAlignment() .getSequences()); @@ -2308,6 +2311,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, void trimAlignment(boolean trimLeft) { + AlignmentI al = viewport.getAlignment(); + ViewportRanges ranges = viewport.getRanges(); ColumnSelection colSel = viewport.getColumnSelection(); int column; @@ -2330,20 +2335,20 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, } else { - seqs = viewport.getAlignment().getSequencesArray(); + seqs = al.getSequencesArray(); } TrimRegionCommand trimRegion; if (trimLeft) { trimRegion = new TrimRegionCommand("Remove Left", true, seqs, - column, viewport.getAlignment()); - viewport.setStartRes(0); + column, al); + ranges.setStartRes(0); } else { trimRegion = new TrimRegionCommand("Remove Right", false, seqs, - column, viewport.getAlignment()); + column, al); } statusBar.setText(MessageManager.formatMessage( @@ -2352,23 +2357,25 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, .toString() })); addHistoryItem(trimRegion); - for (SequenceGroup sg : viewport.getAlignment().getGroups()) + for (SequenceGroup sg : al.getGroups()) { if ((trimLeft && !sg.adjustForRemoveLeft(column)) || (!trimLeft && !sg.adjustForRemoveRight(column))) { - viewport.getAlignment().deleteGroup(sg); + al.deleteGroup(sg); } } - viewport.firePropertyChange("alignment", null, viewport - .getAlignment().getSequences()); + viewport.firePropertyChange("alignment", null, al.getSequences()); } } public void removeGappedColumnMenuItem_actionPerformed() { - int start = 0, end = viewport.getAlignment().getWidth() - 1; + AlignmentI al = viewport.getAlignment(); + ViewportRanges ranges = viewport.getRanges(); + int start = 0; + int end = ranges.getAbsoluteAlignmentWidth() - 1; SequenceI[] seqs; if (viewport.getSelectionGroup() != null) @@ -2396,22 +2403,24 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, // This is to maintain viewport position on first residue // of first sequence - SequenceI seq = viewport.getAlignment().getSequenceAt(0); - int startRes = seq.findPosition(viewport.startRes); + SequenceI seq = al.getSequenceAt(0); + int startRes = seq.findPosition(ranges.getStartRes()); // ShiftList shifts; // viewport.getAlignment().removeGaps(shifts=new ShiftList()); // edit.alColumnChanges=shifts.getInverse(); // if (viewport.hasHiddenColumns) // viewport.getColumnSelection().compensateForEdits(shifts); - viewport.setStartRes(seq.findIndex(startRes) - 1); - viewport.firePropertyChange("alignment", null, viewport.getAlignment() - .getSequences()); + ranges.setStartRes(seq.findIndex(startRes) - 1); + viewport.firePropertyChange("alignment", null, al.getSequences()); } public void removeAllGapsMenuItem_actionPerformed() { - int start = 0, end = viewport.getAlignment().getWidth() - 1; + AlignmentI al = viewport.getAlignment(); + ViewportRanges ranges = viewport.getRanges(); + int start = 0; + int end = ranges.getAbsoluteAlignmentWidth() - 1; SequenceI[] seqs; if (viewport.getSelectionGroup() != null) @@ -2428,16 +2437,15 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, // This is to maintain viewport position on first residue // of first sequence - SequenceI seq = viewport.getAlignment().getSequenceAt(0); - int startRes = seq.findPosition(viewport.startRes); + SequenceI seq = al.getSequenceAt(0); + int startRes = seq.findPosition(ranges.getStartRes()); addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end, - viewport.getAlignment())); + al)); - viewport.setStartRes(seq.findIndex(startRes) - 1); + ranges.setStartRes(seq.findIndex(startRes) - 1); - viewport.firePropertyChange("alignment", null, viewport.getAlignment() - .getSequences()); + viewport.firePropertyChange("alignment", null, al.getSequences()); } diff --git a/src/jalview/appletgui/AlignViewport.java b/src/jalview/appletgui/AlignViewport.java index fc087c6..065c503 100644 --- a/src/jalview/appletgui/AlignViewport.java +++ b/src/jalview/appletgui/AlignViewport.java @@ -35,16 +35,16 @@ import jalview.datamodel.SequenceI; import jalview.renderer.ResidueShader; import jalview.schemes.ColourSchemeProperty; import jalview.schemes.UserColourScheme; -import jalview.structure.CommandListener; import jalview.structure.SelectionSource; import jalview.structure.StructureSelectionManager; import jalview.structure.VamsasSource; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import java.awt.Font; public class AlignViewport extends AlignmentViewport implements - SelectionSource, VamsasSource, CommandListener + SelectionSource { boolean cursorMode = false; @@ -75,12 +75,10 @@ public class AlignViewport extends AlignmentViewport implements calculator = new jalview.workers.AlignCalcManager(); this.applet = applet; alignment = al; + ranges = new ViewportRanges(this.alignment); // we always pad gaps this.setPadGaps(true); - this.startRes = 0; - this.endRes = al.getWidth() - 1; - this.startSeq = 0; - this.endSeq = al.getHeight() - 1; + if (applet != null) { // get the width and height scaling factors if they were specified @@ -299,7 +297,7 @@ public class AlignViewport extends AlignmentViewport implements public void resetSeqLimits(int height) { - setEndSeq(height / getCharHeight()); + ranges.setEndSeq(height / getCharHeight()); } public void setCurrentTree(NJTree tree) diff --git a/src/jalview/appletgui/AlignmentPanel.java b/src/jalview/appletgui/AlignmentPanel.java index e97c347..3ae0394 100644 --- a/src/jalview/appletgui/AlignmentPanel.java +++ b/src/jalview/appletgui/AlignmentPanel.java @@ -28,6 +28,7 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceI; import jalview.structure.StructureSelectionManager; +import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Color; @@ -65,6 +66,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, AnnotationLabels alabels; + ViewportRanges vpRanges; + // this value is set false when selection area being dragged boolean fastPaint = true; @@ -73,6 +76,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, { alignFrame = null; av = null; + vpRanges = null; seqPanel = null; seqPanelHolder = null; sequenceHolderPanel = null; @@ -96,6 +100,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, alignFrame = af; this.av = av; + vpRanges = av.getRanges(); seqPanel = new SeqPanel(av, this); idPanel = new IdPanel(av, this); scalePanel = new ScalePanel(av, this); @@ -126,7 +131,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, @Override public void componentResized(ComponentEvent evt) { - setScrollValues(av.getStartRes(), av.getStartSeq()); + setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq()); if (getSize().height > 0 && annotationPanelHolder.getSize().height > 0) { @@ -383,7 +388,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, */ if (centre) { - int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1; + int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1; start = Math.max(start - offset, 0); end = Math.min(end + offset, seq.getEnd() - 1); } @@ -468,33 +473,34 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, // setScrollValues(start, seqIndex); // } // logic copied from jalview.gui.AlignmentPanel: - if ((startv = av.getStartRes()) >= start) + if ((startv = vpRanges.getStartRes()) >= start) { /* * Scroll left to make start of search results visible */ setScrollValues(start - 1, seqIndex); } - else if ((endv = av.getEndRes()) <= end) + else if ((endv = vpRanges.getEndRes()) <= end) { /* * Scroll right to make end of search results visible */ setScrollValues(startv + 1 + end - endv, seqIndex); } - else if ((starts = av.getStartSeq()) > seqIndex) + else if ((starts = vpRanges.getStartSeq()) > seqIndex) { /* * Scroll up to make start of search results visible */ - setScrollValues(av.getStartRes(), seqIndex); + setScrollValues(vpRanges.getStartRes(), seqIndex); } - else if ((ends = av.getEndSeq()) <= seqIndex) + else if ((ends = vpRanges.getEndSeq()) <= seqIndex) { /* * Scroll down to make end of search results visible */ - setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1); + setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends + + 1); } /* * Else results are already visible - no need to scroll @@ -516,10 +522,11 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, { int cwidth = seqPanel.seqCanvas .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width); - if (res <= av.getStartRes() || res >= (av.getStartRes() + cwidth)) + if (res <= vpRanges.getStartRes() + || res >= (vpRanges.getStartRes() + cwidth)) { vscroll.setValue(res / cwidth); - av.startRes = vscroll.getValue() * cwidth; + vpRanges.setStartRes(vscroll.getValue() * cwidth); } } @@ -632,8 +639,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, public void setWrapAlignment(boolean wrap) { - av.startSeq = 0; - av.startRes = 0; + vpRanges.setStartSeq(0); + vpRanges.setStartRes(0); scalePanelHolder.setVisible(!wrap); hscroll.setVisible(!wrap); @@ -724,7 +731,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, { x = 0; } - ; + hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth(); vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight(); @@ -762,17 +769,10 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, x = 0; } - av.setStartSeq(y); - - int endSeq = y + vextent; - if (endSeq > av.getAlignment().getHeight()) - { - endSeq = av.getAlignment().getHeight(); - } - - av.setEndSeq(endSeq); - av.setStartRes(x); - av.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av + vpRanges.setStartSeq(y); + vpRanges.setEndSeq(y + vextent); + vpRanges.setStartRes(x); + vpRanges.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av .getCharWidth())) - 1); hscroll.setValues(x, hextent, 0, width); @@ -789,8 +789,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, @Override public void adjustmentValueChanged(AdjustmentEvent evt) { - int oldX = av.getStartRes(); - int oldY = av.getStartSeq(); + int oldX = vpRanges.getStartRes(); + int oldY = vpRanges.getStartSeq(); if (evt == null || evt.getSource() == apvscroll) { @@ -804,8 +804,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, if (evt == null || evt.getSource() == hscroll) { int x = hscroll.getValue(); - av.setStartRes(x); - av.setEndRes(x + seqPanel.seqCanvas.getSize().width + vpRanges.setStartRes(x); + vpRanges.setEndRes(x + seqPanel.seqCanvas.getSize().width / av.getCharWidth() - 1); } @@ -816,14 +816,14 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, { int rowSize = seqPanel.seqCanvas .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width); - av.setStartRes(vscroll.getValue() * rowSize); - av.setEndRes((vscroll.getValue() + 1) * rowSize); + vpRanges.setStartRes(vscroll.getValue() * rowSize); + vpRanges.setEndRes((vscroll.getValue() + 1) * rowSize); } else { - av.setStartSeq(offy); - av.setEndSeq(offy + seqPanel.seqCanvas.getSize().height - / av.getCharHeight()); + vpRanges.setStartSeq(offy); + vpRanges.setEndSeq(offy + seqPanel.seqCanvas.getSize().height + / av.getCharHeight() - 1); } } @@ -832,8 +832,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, overviewPanel.setBoxPosition(); } - int scrollX = av.startRes - oldX; - int scrollY = av.startSeq - oldY; + int scrollX = vpRanges.getStartRes() - oldX; + int scrollY = vpRanges.getStartSeq() - oldY; if (av.getWrapAlignment() || !fastPaint || av.MAC) { @@ -843,13 +843,13 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, { // Make sure we're not trying to draw a panel // larger than the visible window - if (scrollX > av.endRes - av.startRes) + if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes()) { - scrollX = av.endRes - av.startRes; + scrollX = vpRanges.getEndRes() - vpRanges.getStartRes(); } - else if (scrollX < av.startRes - av.endRes) + else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes()) { - scrollX = av.startRes - av.endRes; + scrollX = vpRanges.getStartRes() - vpRanges.getEndRes(); } idPanel.idCanvas.fastPaint(scrollY); @@ -858,7 +858,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, scalePanel.repaint(); if (av.isShowAnnotation()) { - annotationPanel.fastPaint(av.getStartRes() - oldX); + annotationPanel.fastPaint(vpRanges.getStartRes() - oldX); } } sendViewPosition(); @@ -955,8 +955,9 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, private void sendViewPosition() { StructureSelectionManager.getStructureSelectionManager(av.applet) - .sendViewPosition(this, av.startRes, av.endRes, av.startSeq, - av.endSeq); + .sendViewPosition(this, vpRanges.getStartRes(), + vpRanges.getEndRes(), vpRanges.getStartSeq(), + vpRanges.getEndSeq()); } /** @@ -1024,7 +1025,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener, } else { - setScrollValues(av.getStartRes(), av.getStartSeq()); + setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq()); } seqPanel.seqCanvas.repaint(); diff --git a/src/jalview/appletgui/AnnotationLabels.java b/src/jalview/appletgui/AnnotationLabels.java index b28ccc7..ad74b25 100755 --- a/src/jalview/appletgui/AnnotationLabels.java +++ b/src/jalview/appletgui/AnnotationLabels.java @@ -339,7 +339,8 @@ public class AnnotationLabels extends Panel implements ActionListener, av.calcPanelHeight()); f.height += dif; ap.seqPanelHolder.setPreferredSize(f); - ap.setScrollValues(av.getStartRes(), av.getStartSeq()); + ap.setScrollValues(av.getRanges().getStartRes(), av.getRanges() + .getStartSeq()); ap.validate(); // ap.paintAlignment(true); ap.addNotify(); diff --git a/src/jalview/appletgui/AnnotationPanel.java b/src/jalview/appletgui/AnnotationPanel.java index 6012c1a..0ec7adf 100755 --- a/src/jalview/appletgui/AnnotationPanel.java +++ b/src/jalview/appletgui/AnnotationPanel.java @@ -462,7 +462,8 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI, } } - int column = evt.getX() / av.getCharWidth() + av.getStartRes(); + int column = evt.getX() / av.getCharWidth() + + av.getRanges().getStartRes(); if (av.hasHiddenColumns()) { @@ -618,7 +619,8 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI, gg.setColor(Color.white); gg.fillRect(0, 0, getSize().width, getSize().height); - drawComponent(gg, av.startRes, av.endRes + 1); + drawComponent(gg, av.getRanges().getStartRes(), av.getRanges() + .getEndRes() + 1); g.drawImage(image, 0, 0, this); } @@ -635,7 +637,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI, gg.copyArea(0, 0, imgWidth, getSize().height, -horizontal * av.getCharWidth(), 0); - int sr = av.startRes, er = av.endRes + 1, transX = 0; + int sr = av.getRanges().getStartRes(), er = av.getRanges().getEndRes() + 1, transX = 0; if (horizontal > 0) // scrollbar pulled right, image to the left { diff --git a/src/jalview/appletgui/AppletJmolBinding.java b/src/jalview/appletgui/AppletJmolBinding.java index f938cad..9b8a235 100644 --- a/src/jalview/appletgui/AppletJmolBinding.java +++ b/src/jalview/appletgui/AppletJmolBinding.java @@ -54,21 +54,7 @@ class AppletJmolBinding extends JalviewJmolBinding public jalview.api.FeatureRenderer getFeatureRenderer( AlignmentViewPanel alignment) { - AlignmentPanel ap = (AlignmentPanel) alignment; - if (appletJmolBinding.ap.av.isShowSequenceFeatures()) - { - if (appletJmolBinding.fr == null) - { - appletJmolBinding.fr = new jalview.appletgui.FeatureRenderer( - appletJmolBinding.ap.av); - } - - appletJmolBinding.fr - .transferSettings(appletJmolBinding.ap.seqPanel.seqCanvas - .getFeatureRenderer()); - } - - return appletJmolBinding.fr; + return appletJmolBinding.ap.getFeatureRenderer(); } @Override diff --git a/src/jalview/appletgui/ExtJmol.java b/src/jalview/appletgui/ExtJmol.java index 189fe88..b369318 100644 --- a/src/jalview/appletgui/ExtJmol.java +++ b/src/jalview/appletgui/ExtJmol.java @@ -82,10 +82,10 @@ public class ExtJmol extends JalviewJmolBinding @Override public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment) { - AlignmentPanel ap = (AlignmentPanel) alignment; - if (ap.av.isShowSequenceFeatures()) + AlignmentPanel alignPanel = (AlignmentPanel) alignment; + if (alignPanel.av.isShowSequenceFeatures()) { - return ap.getFeatureRenderer(); + return alignPanel.getFeatureRenderer(); } else { diff --git a/src/jalview/appletgui/FeatureRenderer.java b/src/jalview/appletgui/FeatureRenderer.java index 67ca8e9..b88a1dc 100644 --- a/src/jalview/appletgui/FeatureRenderer.java +++ b/src/jalview/appletgui/FeatureRenderer.java @@ -377,9 +377,6 @@ public class FeatureRenderer extends if (dialog.accept) { - // This ensures that the last sequence - // is refreshed and new features are rendered - lastSeq = null; lastFeatureAdded = name.getText().trim(); lastFeatureGroupAdded = source.getText().trim(); lastDescriptionAdded = description.getText().replace('\n', ' '); diff --git a/src/jalview/appletgui/FeatureSettings.java b/src/jalview/appletgui/FeatureSettings.java index 2c454a4..1b9fbf9 100755 --- a/src/jalview/appletgui/FeatureSettings.java +++ b/src/jalview/appletgui/FeatureSettings.java @@ -696,8 +696,7 @@ public class FeatureSettings extends Panel implements ItemListener, public void adjustmentValueChanged(AdjustmentEvent evt) { fr.setTransparency((100 - transparency.getValue()) / 100f); - ap.seqPanel.seqCanvas.repaint(); - + ap.paintAlignment(true); } class MyCheckbox extends Checkbox diff --git a/src/jalview/appletgui/IdCanvas.java b/src/jalview/appletgui/IdCanvas.java index d72e91f..abcbd70 100755 --- a/src/jalview/appletgui/IdCanvas.java +++ b/src/jalview/appletgui/IdCanvas.java @@ -21,6 +21,7 @@ package jalview.appletgui; import jalview.datamodel.SequenceI; +import jalview.viewmodel.ViewportRanges; import java.awt.Color; import java.awt.Font; @@ -103,28 +104,32 @@ public class IdCanvas extends Panel return; } + ViewportRanges ranges = av.getRanges(); + gg.copyArea(0, 0, getSize().width, imgHeight, 0, -vertical * av.getCharHeight()); - int ss = av.startSeq, es = av.endSeq, transY = 0; + int ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transY = 0; if (vertical > 0) // scroll down { ss = es - vertical; - if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time + if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page + // at a + // time { - ss = av.startSeq; + ss = ranges.getStartSeq(); } else { - transY = imgHeight - vertical * av.getCharHeight(); + transY = imgHeight - ((vertical + 1) * av.getCharHeight()); } } else if (vertical < 0) { es = ss - vertical; - if (es > av.endSeq) + if (es > ranges.getEndSeq()) { - es = av.endSeq; + es = ranges.getEndSeq(); } } @@ -180,7 +185,7 @@ public class IdCanvas extends Panel gg.setFont(italic); gg.fillRect(0, 0, getSize().width, getSize().height); - drawIds(av.startSeq, av.endSeq); + drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq()); g.drawImage(image, 0, 0, this); } @@ -233,9 +238,10 @@ public class IdCanvas extends Panel int cHeight = alheight * avcharHeight + hgap + annotationHeight; - int rowSize = av.getEndRes() - av.getStartRes(); + int rowSize = av.getRanges().getEndRes() + - av.getRanges().getStartRes(); // Draw the rest of the panels - for (int ypos = hgap, row = av.startRes; (ypos <= getSize().height) + for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getSize().height) && (row < maxwidth); ypos += cHeight, row += rowSize) { for (int i = starty; i < alheight; i++) @@ -263,7 +269,7 @@ public class IdCanvas extends Panel { // Now draw the id strings SequenceI seq; - for (int i = starty; i < endy; i++) + for (int i = starty; i <= endy; i++) { seq = av.getAlignment().getSequenceAt(i); diff --git a/src/jalview/appletgui/IdPanel.java b/src/jalview/appletgui/IdPanel.java index 182f20e..e47c50a 100755 --- a/src/jalview/appletgui/IdPanel.java +++ b/src/jalview/appletgui/IdPanel.java @@ -20,14 +20,13 @@ */ package jalview.appletgui; -import static jalview.util.UrlConstants.EMBLEBI_STRING; -import static jalview.util.UrlConstants.SRS_STRING; - import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; -import jalview.util.UrlLink; +import jalview.urls.api.UrlProviderFactoryI; +import jalview.urls.api.UrlProviderI; +import jalview.urls.applet.AppletUrlProviderFactory; import jalview.viewmodel.AlignmentViewport; import java.awt.BorderLayout; @@ -36,8 +35,8 @@ import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; +import java.util.HashMap; import java.util.List; -import java.util.Vector; public class IdPanel extends Panel implements MouseListener, MouseMotionListener @@ -55,7 +54,7 @@ public class IdPanel extends Panel implements MouseListener, boolean mouseDragging = false; - java.util.Vector links = new java.util.Vector(); + UrlProviderI urlProvider = null; public IdPanel(AlignViewport av, AlignmentPanel parent) { @@ -69,6 +68,9 @@ public class IdPanel extends Panel implements MouseListener, String label, url; // TODO: add in group link parameter + + // make a list of label,url pairs + HashMap urlList = new HashMap(); if (av.applet != null) { for (int i = 1; i < 10; i++) @@ -76,26 +78,22 @@ public class IdPanel extends Panel implements MouseListener, label = av.applet.getParameter("linkLabel_" + i); url = av.applet.getParameter("linkURL_" + i); - if (label != null && url != null) + // only add non-null parameters + if (label != null) { - links.addElement(label + "|" + url); + urlList.put(label, url); } - } - } - { - // upgrade old SRS link - int srsPos = links.indexOf(SRS_STRING); - if (srsPos > -1) + + if (!urlList.isEmpty()) { - links.setElementAt(EMBLEBI_STRING, srsPos); + // set default as first entry in list + String defaultUrl = av.applet.getParameter("linkLabel_1"); + UrlProviderFactoryI factory = new AppletUrlProviderFactory( + defaultUrl, urlList); + urlProvider = factory.createUrlProvider(); } } - if (links.size() < 1) - { - links = new java.util.Vector(); - links.addElement(EMBLEBI_STRING); - } } Tooltip tooltip; @@ -217,7 +215,7 @@ public class IdPanel extends Panel implements MouseListener, return; } - // DEFAULT LINK IS FIRST IN THE LINK LIST + // get the sequence details int seq = alignPanel.seqPanel.findSeq(e); SequenceI sq = av.getAlignment().getSequenceAt(seq); if (sq == null) @@ -226,53 +224,11 @@ public class IdPanel extends Panel implements MouseListener, } String id = sq.getName(); - String target = null; - String url = null; - int i = 0; - while (url == null && i < links.size()) - { - // DEFAULT LINK IS FIRST IN THE LINK LIST - // BUT IF ITS A REGEX AND DOES NOT MATCH THE NEXT ONE WILL BE TRIED - url = links.elementAt(i++).toString(); - jalview.util.UrlLink urlLink = null; - try - { - urlLink = new UrlLink(url); - target = urlLink.getTarget(); - } catch (Exception foo) - { - System.err.println("Exception for URLLink '" + url + "'"); - foo.printStackTrace(); - url = null; - continue; - } - - if (urlLink.usesDBAccession()) - { - // this URL requires an accession id, not the name of a sequence - url = null; - continue; - } - - if (!urlLink.isValid()) - { - System.err.println(urlLink.getInvalidMessage()); - url = null; - continue; - } - - String urls[] = urlLink.makeUrls(id, true); - if (urls == null || urls[0] == null || urls[0].length() < 1) - { - url = null; - continue; - } - // just take first URL made from regex - url = urls[1]; - } + // get the default url with the sequence details filled in + String url = urlProvider.getPrimaryUrl(id); + String target = urlProvider.getPrimaryTarget(id); try { - alignPanel.alignFrame.showURL(url, target); } catch (Exception ex) { @@ -297,13 +253,13 @@ public class IdPanel extends Panel implements MouseListener, return; } - if (mouseDragging && e.getY() < 0 && av.getStartSeq() > 0) + if (mouseDragging && e.getY() < 0 && av.getRanges().getStartSeq() > 0) { scrollThread = new ScrollThread(true); } if (mouseDragging && e.getY() >= getSize().height - && av.getAlignment().getHeight() > av.getEndSeq()) + && av.getAlignment().getHeight() > av.getRanges().getEndSeq()) { scrollThread = new ScrollThread(false); } @@ -331,11 +287,8 @@ public class IdPanel extends Panel implements MouseListener, // build a new links menu based on the current links + any non-positional // features - Vector nlinks = new Vector(); - for (int l = 0, lSize = links.size(); l < lSize; l++) - { - nlinks.addElement(links.elementAt(l)); - } + List nlinks = urlProvider.getLinksForMenu(); + SequenceFeature sf[] = sq == null ? null : sq.getSequenceFeatures(); for (int sl = 0; sf != null && sl < sf.length; sl++) { @@ -345,7 +298,7 @@ public class IdPanel extends Panel implements MouseListener, { for (int l = 0, lSize = sf[sl].links.size(); l < lSize; l++) { - nlinks.addElement(sf[sl].links.elementAt(l)); + nlinks.add(sf[sl].links.elementAt(l)); } } } @@ -445,9 +398,10 @@ public class IdPanel extends Panel implements MouseListener, int index = av.getAlignment().findIndex(list.get(0)); // do we need to scroll the panel? - if (av.getStartSeq() > index || av.getEndSeq() < index) + if (av.getRanges().getStartSeq() > index + || av.getRanges().getEndSeq() < index) { - alignPanel.setScrollValues(av.getStartRes(), index); + alignPanel.setScrollValues(av.getRanges().getStartRes(), index); } } @@ -478,10 +432,10 @@ public class IdPanel extends Panel implements MouseListener, if (alignPanel.scrollUp(up)) { // scroll was ok, so add new sequence to selection - int seq = av.getStartSeq(); + int seq = av.getRanges().getStartSeq(); if (!up) { - seq = av.getEndSeq(); + seq = av.getRanges().getEndSeq(); } if (seq < lastid) diff --git a/src/jalview/appletgui/OverviewPanel.java b/src/jalview/appletgui/OverviewPanel.java index 9b2be4c..3ef2936 100755 --- a/src/jalview/appletgui/OverviewPanel.java +++ b/src/jalview/appletgui/OverviewPanel.java @@ -20,7 +20,9 @@ */ package jalview.appletgui; -import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureColourFinder; +import jalview.viewmodel.OverviewDimensions; import java.awt.Color; import java.awt.Dimension; @@ -37,38 +39,34 @@ import java.awt.event.MouseMotionListener; public class OverviewPanel extends Panel implements Runnable, MouseMotionListener, MouseListener { - Image miniMe; + private OverviewDimensions od; - Image offscreen; + private Image miniMe; - AlignViewport av; + private Image offscreen; - AlignmentPanel ap; + private AlignViewport av; - float scalew = 1f; + private AlignmentPanel ap; - float scaleh = 1f; + private boolean resizing = false; - public int width, sequencesHeight; - - int graphHeight = 20; - - int boxX = -1, boxY = -1, boxWidth = -1, boxHeight = -1; - - boolean resizing = false; + // This is set true if the user resizes whilst + // the overview is being calculated + private boolean resizeAgain = false; // Can set different properties in this seqCanvas than // main visible SeqCanvas - SequenceRenderer sr; + private SequenceRenderer sr; - FeatureRenderer fr; + private FeatureRenderer fr; - Frame nullFrame; + private Frame nullFrame; - public OverviewPanel(AlignmentPanel ap) + public OverviewPanel(AlignmentPanel alPanel) { - this.av = ap.av; - this.ap = ap; + this.av = alPanel.av; + this.ap = alPanel; setLayout(null); nullFrame = new Frame(); nullFrame.addNotify(); @@ -79,45 +77,18 @@ public class OverviewPanel extends Panel implements Runnable, sr.forOverview = true; fr = new FeatureRenderer(av); - // scale the initial size of overviewpanel to shape of alignment - float initialScale = (float) av.getAlignment().getWidth() - / (float) av.getAlignment().getHeight(); - - if (av.getSequenceConsensusHash() == null) - { - graphHeight = 0; - } - - if (av.getAlignment().getWidth() > av.getAlignment().getHeight()) - { - // wider - width = 400; - sequencesHeight = (int) (400f / initialScale); - if (sequencesHeight < 40) - { - sequencesHeight = 40; - } - } - else - { - // taller - width = (int) (400f * initialScale); - sequencesHeight = 300; - if (width < 120) - { - width = 120; - } - } + od = new OverviewDimensions(av.getRanges(), + (av.isShowAnnotation() && av.getSequenceConsensusHash() != null)); - setSize(new Dimension(width, sequencesHeight + graphHeight)); + setSize(new Dimension(od.getWidth(), od.getHeight())); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent evt) { - if (getSize().width != width - || getSize().height != sequencesHeight + graphHeight) + if ((getWidth() != od.getWidth()) + || (getHeight() != (od.getHeight()))) { updateOverviewImage(); } @@ -155,79 +126,32 @@ public class OverviewPanel extends Panel implements Runnable, @Override public void mousePressed(MouseEvent evt) { - boxX = evt.getX(); - boxY = evt.getY(); - checkValid(); + mouseAction(evt); } @Override public void mouseReleased(MouseEvent evt) { - boxX = evt.getX(); - boxY = evt.getY(); - checkValid(); + mouseAction(evt); } @Override public void mouseDragged(MouseEvent evt) { - boxX = evt.getX(); - boxY = evt.getY(); - checkValid(); + mouseAction(evt); } - void checkValid() + private void mouseAction(MouseEvent evt) { - if (boxY < 0) - { - boxY = 0; - } - - if (boxY > (sequencesHeight - boxHeight)) - { - boxY = sequencesHeight - boxHeight + 1; - } - - if (boxX < 0) - { - boxX = 0; - } - - if (boxX > (width - boxWidth)) - { - if (av.hasHiddenColumns()) - { - // Try smallest possible box - boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew); - } - boxX = width - boxWidth; - } - - int col = (int) (boxX / scalew / av.getCharWidth()); - int row = (int) (boxY / scaleh / av.getCharHeight()); - - if (av.hasHiddenColumns()) - { - if (!av.getColumnSelection().isVisible(col)) - { - return; - } - - col = av.getColumnSelection().findColumnPosition(col); - } - - if (av.hasHiddenRows()) - { - row = av.getAlignment().getHiddenSequences() - .findIndexWithoutHiddenSeqs(row); - } - - ap.setScrollValues(col, row); + od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment() + .getHiddenSequences(), av.getColumnSelection(), av + .getRanges()); + ap.setScrollValues(od.getScrollCol(), od.getScrollRow()); ap.paintAlignment(false); } /** - * DOCUMENT ME! + * Updates the overview image when the related alignment panel is updated */ public void updateOverviewImage() { @@ -246,27 +170,20 @@ public class OverviewPanel extends Panel implements Runnable, if ((getSize().width > 0) && (getSize().height > 0)) { - width = getSize().width; - sequencesHeight = getSize().height - graphHeight; + od.setWidth(getSize().width); + od.setHeight(getSize().height); } - setSize(new Dimension(width, sequencesHeight + graphHeight)); + setSize(new Dimension(od.getWidth(), od.getHeight())); Thread thread = new Thread(this); thread.start(); repaint(); } - // This is set true if the user resizes whilst - // the overview is being calculated - boolean resizeAgain = false; - @Override public void run() { miniMe = null; - int alwidth = av.getAlignment().getWidth(); - int alheight = av.getAlignment().getHeight() - + av.getAlignment().getHiddenSequences().getSize(); if (av.isShowSequenceFeatures()) { @@ -275,135 +192,37 @@ public class OverviewPanel extends Panel implements Runnable, if (getSize().width > 0 && getSize().height > 0) { - width = getSize().width; - sequencesHeight = getSize().height - graphHeight; + od.setWidth(getSize().width); + od.setHeight(getSize().height); } - setSize(new Dimension(width, sequencesHeight + graphHeight)); - - int fullsizeWidth = alwidth * av.getCharWidth(); - int fullsizeHeight = alheight * av.getCharHeight(); + setSize(new Dimension(od.getWidth(), od.getHeight())); - scalew = (float) width / (float) fullsizeWidth; - scaleh = (float) sequencesHeight / (float) fullsizeHeight; - - miniMe = nullFrame.createImage(width, sequencesHeight + graphHeight); - offscreen = nullFrame.createImage(width, sequencesHeight + graphHeight); + miniMe = nullFrame.createImage(od.getWidth(), od.getHeight()); + offscreen = nullFrame.createImage(od.getWidth(), od.getHeight()); Graphics mg = miniMe.getGraphics(); - float sampleCol = (float) alwidth / (float) width; - float sampleRow = (float) alheight / (float) sequencesHeight; - - int lastcol = 0, lastrow = 0; - int xstart = 0, ystart = 0; - Color color = Color.yellow; - int row, col, sameRow = 0, sameCol = 0; - jalview.datamodel.SequenceI seq; - final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av - .hasHiddenColumns(); - boolean hiddenRow = false; - AlignmentI alignment = av.getAlignment(); - for (row = 0; row <= sequencesHeight; row++) - { - if (resizeAgain) - { - break; - } - if ((int) (row * sampleRow) == lastrow) - { - sameRow++; - continue; - } - - hiddenRow = false; - if (hasHiddenRows) - { - seq = alignment.getHiddenSequences().getHiddenSequence(lastrow); - if (seq == null) - { - int index = alignment.getHiddenSequences() - .findIndexWithoutHiddenSeqs(lastrow); - - seq = alignment.getSequenceAt(index); - } - else - { - hiddenRow = true; - } - } - else - { - seq = alignment.getSequenceAt(lastrow); - } - - for (col = 0; col < width; col++) - { - if ((int) (col * sampleCol) == lastcol - && (int) (row * sampleRow) == lastrow) - { - sameCol++; - continue; - } - - lastcol = (int) (col * sampleCol); - - if (seq.getLength() > lastcol) - { - color = sr.getResidueBoxColour(seq, lastcol); - - if (av.isShowSequenceFeatures()) - { - color = fr.findFeatureColour(color, seq, lastcol); - } - } - else - { - color = Color.white; // White - } - - if (hiddenRow - || (hasHiddenCols && !av.getColumnSelection().isVisible( - lastcol))) - { - color = color.darker().darker(); - } - mg.setColor(color); - if (sameCol == 1 && sameRow == 1) - { - mg.drawLine(xstart, ystart, xstart, ystart); - } - else - { - mg.fillRect(xstart, ystart, sameCol, sameRow); - } + int alwidth = av.getAlignment().getWidth(); + int alheight = av.getAlignment().getAbsoluteHeight(); + float sampleCol = alwidth / (float) od.getWidth(); + float sampleRow = alheight / (float) od.getSequencesHeight(); - xstart = col; - sameCol = 1; - } - lastrow = (int) (row * sampleRow); - ystart = row; - sameRow = 1; - } + buildImage(sampleRow, sampleCol, mg); - if (av.getAlignmentConservationAnnotation() != null) + // check for conservation annotation to make sure overview works for DNA too + if (av.isShowAnnotation() + && (av.getAlignmentConservationAnnotation() != null)) { - for (col = 0; col < width; col++) + for (int col = 0; col < od.getWidth() && !resizeAgain; col++) { - if (resizeAgain) - { - break; - } - lastcol = (int) (col * sampleCol); - { - mg.translate(col, sequencesHeight); - ap.annotationPanel.renderer.drawGraph(mg, - av.getAlignmentConservationAnnotation(), - av.getAlignmentConservationAnnotation().annotations, - (int) (sampleCol) + 1, graphHeight, - (int) (col * sampleCol), (int) (col * sampleCol) + 1); - mg.translate(-col, -sequencesHeight); - } + mg.translate(col, od.getSequencesHeight()); + ap.annotationPanel.renderer.drawGraph(mg, + av.getAlignmentConservationAnnotation(), + av.getAlignmentConservationAnnotation().annotations, + (int) (sampleCol) + 1, od.getGraphHeight(), + (int) (col * sampleCol), (int) (col * sampleCol) + 1); + mg.translate(-col, -od.getSequencesHeight()); } } System.gc(); @@ -419,52 +238,104 @@ public class OverviewPanel extends Panel implements Runnable, } } - public void setBoxPosition() + /* + * Build the overview panel image + */ + private void buildImage(float sampleRow, float sampleCol, Graphics mg) { - int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth(); - int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment() - .getHiddenSequences().getSize()) - * av.getCharHeight(); - - int startRes = av.getStartRes(); - int endRes = av.getEndRes(); + int lastcol = 0; + int lastrow = 0; + int xstart = 0; + int ystart = 0; + Color color = Color.yellow; + int sameRow = 0; + int sameCol = 0; - if (av.hasHiddenColumns()) - { - startRes = av.getColumnSelection().adjustForHiddenColumns(startRes); - endRes = av.getColumnSelection().adjustForHiddenColumns(endRes); - } + SequenceI seq = null; + FeatureColourFinder finder = new FeatureColourFinder(fr); - int startSeq = av.startSeq; - int endSeq = av.endSeq; + final boolean hasHiddenCols = av.hasHiddenColumns(); + boolean hiddenRow = false; - if (av.hasHiddenRows()) + for (int row = 0; row <= od.getSequencesHeight() && !resizeAgain; row++) { - startSeq = av.getAlignment().getHiddenSequences() - .adjustForHiddenSeqs(startSeq); - - endSeq = av.getAlignment().getHiddenSequences() - .adjustForHiddenSeqs(endSeq); + if ((int) (row * sampleRow) == lastrow) + { + sameRow++; + } + else + { + // get the sequence which would be at alignment index 'lastrow' if no + // columns were hidden, and determine whether it is hidden or not + hiddenRow = av.getAlignment().isHidden(lastrow); + seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow); + for (int col = 0; col < od.getWidth(); col++) + { + if ((int) (col * sampleCol) == lastcol + && (int) (row * sampleRow) == lastrow) + { + sameCol++; + } + else + { + lastcol = (int) (col * sampleCol); + + color = getColumnColourFromSequence(seq, hiddenRow, + hasHiddenCols, lastcol, finder); + + mg.setColor(color); + if (sameCol == 1 && sameRow == 1) + { + mg.drawLine(xstart, ystart, xstart, ystart); + } + else + { + mg.fillRect(xstart, ystart, sameCol, sameRow); + } + + xstart = col; + sameCol = 1; + } + } + lastrow = (int) (row * sampleRow); + ystart = row; + sameRow = 1; + } } + } - scalew = (float) width / (float) fullsizeWidth; - scaleh = (float) sequencesHeight / (float) fullsizeHeight; - - boxX = (int) (startRes * av.getCharWidth() * scalew); - boxY = (int) (startSeq * av.getCharHeight() * scaleh); - - if (av.hasHiddenColumns()) + /* + * Find the colour of a sequence at a specified column position + */ + private Color getColumnColourFromSequence( + jalview.datamodel.SequenceI seq, boolean hiddenRow, + boolean hasHiddenCols, int lastcol, FeatureColourFinder finder) + { + Color color = Color.white; + if (seq.getLength() > lastcol) { - boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew); + color = sr.getResidueColour(seq, lastcol, finder); } - else + + if (hiddenRow + || (hasHiddenCols && !av.getColumnSelection() + .isVisible(lastcol))) { - boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew); + color = color.darker().darker(); } + return color; + } - boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh); - + /** + * Update the overview panel box when the associated alignment panel is + * changed + * + */ + public void setBoxPosition() + { + od.setBoxPosition(av.getAlignment() + .getHiddenSequences(), av.getColumnSelection(), av.getRanges()); repaint(); } @@ -482,8 +353,7 @@ public class OverviewPanel extends Panel implements Runnable, { og.drawImage(miniMe, 0, 0, this); og.setColor(Color.red); - og.drawRect(boxX, boxY, boxWidth, boxHeight); - og.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2); + od.drawBox(og); g.drawImage(offscreen, 0, 0, this); } } diff --git a/src/jalview/appletgui/ScalePanel.java b/src/jalview/appletgui/ScalePanel.java index ed07b63..15d82a5 100755 --- a/src/jalview/appletgui/ScalePanel.java +++ b/src/jalview/appletgui/ScalePanel.java @@ -76,7 +76,7 @@ public class ScalePanel extends Panel implements MouseMotionListener, @Override public void mousePressed(MouseEvent evt) { - int x = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes(); final int res; if (av.hasHiddenColumns()) @@ -229,7 +229,8 @@ public class ScalePanel extends Panel implements MouseMotionListener, { mouseDragging = false; - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int res = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); if (res > av.getAlignment().getWidth()) { @@ -276,7 +277,8 @@ public class ScalePanel extends Panel implements MouseMotionListener, mouseDragging = true; ColumnSelection cs = av.getColumnSelection(); - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int res = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); res = Math.max(0, res); res = cs.adjustForHiddenColumns(res); res = Math.min(res, av.getAlignment().getWidth() - 1); @@ -324,7 +326,8 @@ public class ScalePanel extends Panel implements MouseMotionListener, return; } - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int res = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); res = av.getColumnSelection().adjustForHiddenColumns(res); @@ -350,7 +353,8 @@ public class ScalePanel extends Panel implements MouseMotionListener, @Override public void paint(Graphics g) { - drawScale(g, av.getStartRes(), av.getEndRes(), getSize().width, + drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(), + getSize().width, getSize().height); } diff --git a/src/jalview/appletgui/SeqCanvas.java b/src/jalview/appletgui/SeqCanvas.java index 5d6bb07..ed8a46d 100755 --- a/src/jalview/appletgui/SeqCanvas.java +++ b/src/jalview/appletgui/SeqCanvas.java @@ -27,6 +27,7 @@ import jalview.datamodel.SequenceI; import jalview.renderer.ScaleRenderer; import jalview.renderer.ScaleRenderer.ScaleMark; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import java.awt.Color; import java.awt.FontMetrics; @@ -211,17 +212,19 @@ public class SeqCanvas extends Panel return; } + ViewportRanges ranges = av.getRanges(); + updateViewport(); // Its possible on certain browsers that the call to fastpaint // is faster than it can paint, so this check here catches // this possibility - if (lastsr + horizontal != av.startRes) + if (lastsr + horizontal != ranges.getStartRes()) { - horizontal = av.startRes - lastsr; + horizontal = ranges.getStartRes() - lastsr; } - lastsr = av.startRes; + lastsr = ranges.getStartRes(); fastPaint = true; gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight, imgWidth @@ -229,7 +232,9 @@ public class SeqCanvas extends Panel imgHeight - vertical * avcharHeight, -horizontal * avcharWidth, -vertical * avcharHeight); - int sr = av.startRes, er = av.endRes, ss = av.startSeq, es = av.endSeq, transX = 0, transY = 0; + int sr = ranges.getStartRes(), er = ranges.getEndRes(), ss = ranges + .getStartSeq(), es = ranges + .getEndSeq(), transX = 0, transY = 0; if (horizontal > 0) // scrollbar pulled right, image to the left { @@ -244,21 +249,23 @@ public class SeqCanvas extends Panel else if (vertical > 0) // scroll down { ss = es - vertical; - if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time + if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page + // at a + // time { - ss = av.startSeq; + ss = ranges.getStartSeq(); } else { - transY = imgHeight - vertical * avcharHeight; + transY = imgHeight - ((vertical + 1) * avcharHeight); } } else if (vertical < 0) { es = ss - vertical; - if (es > av.endSeq) + if (es > ranges.getEndSeq()) { - es = av.endSeq; + es = ranges.getEndSeq(); } } @@ -329,13 +336,16 @@ public class SeqCanvas extends Panel gg.setColor(Color.white); gg.fillRect(0, 0, imgWidth, imgHeight); + ViewportRanges ranges = av.getRanges(); + if (av.getWrapAlignment()) { - drawWrappedPanel(gg, imgWidth, imgHeight, av.startRes); + drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes()); } else { - drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0); + drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(), + ranges.getStartSeq(), ranges.getEndSeq(), 0); } g.drawImage(img, 0, 0, this); @@ -421,7 +431,7 @@ public class SeqCanvas extends Panel av.setWrappedWidth(cWidth); - av.endRes = av.startRes + cWidth; + av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth); int endx; int ypos = hgap; @@ -610,7 +620,7 @@ public class SeqCanvas extends Panel // / First draw the sequences // /////////////////////////// - for (int i = startSeq; i < endSeq; i++) + for (int i = startSeq; i <= endSeq; i++) { nextSeq = av.getAlignment().getSequenceAt(i); @@ -625,7 +635,7 @@ public class SeqCanvas extends Panel if (av.isShowSequenceFeatures()) { fr.drawSequence(g, nextSeq, startRes, endRes, offset - + ((i - startSeq) * avcharHeight)); + + ((i - startSeq) * avcharHeight), false); } // / Highlight search Results once all sequences have been drawn @@ -694,7 +704,7 @@ public class SeqCanvas extends Panel int bottom = -1; int alHeight = av.getAlignment().getHeight() - 1; - for (i = startSeq; i < endSeq; i++) + for (i = startSeq; i <= endSeq; i++) { sx = (group.getStartRes() - startRes) * avcharWidth; sy = offset + ((i - startSeq) * avcharHeight); diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index 8cfd2dc..0e12703 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -41,6 +41,7 @@ import jalview.structure.VamsasSource; import jalview.util.MappingUtils; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Font; @@ -226,16 +227,17 @@ public class SeqPanel extends Panel implements MouseMotionListener, } else { - while (seqCanvas.cursorY < av.startSeq) + ViewportRanges ranges = av.getRanges(); + while (seqCanvas.cursorY < ranges.getStartSeq()) { ap.scrollUp(true); } - while (seqCanvas.cursorY + 1 > av.endSeq) + while (seqCanvas.cursorY + 1 > ranges.getEndSeq()) { ap.scrollUp(false); } while (seqCanvas.cursorX < av.getColumnSelection() - .adjustForHiddenColumns(av.startRes)) + .adjustForHiddenColumns(ranges.getStartRes())) { if (!ap.scrollRight(false)) @@ -244,7 +246,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, } } while (seqCanvas.cursorX > av.getColumnSelection() - .adjustForHiddenColumns(av.endRes)) + .adjustForHiddenColumns(ranges.getEndRes())) { if (!ap.scrollRight(true)) { @@ -624,14 +626,14 @@ public class SeqPanel extends Panel implements MouseMotionListener, } wrappedBlock = y / cHeight; - wrappedBlock += av.getStartRes() / cwidth; + wrappedBlock += av.getRanges().getStartRes() / cwidth; res = wrappedBlock * cwidth + x / av.getCharWidth(); } else { - res = (x / av.getCharWidth()) + av.getStartRes(); + res = (x / av.getCharWidth()) + av.getRanges().getStartRes(); } if (av.hasHiddenColumns()) @@ -681,7 +683,9 @@ public class SeqPanel extends Panel implements MouseMotionListener, } else { - seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av + seq = Math.min((y / av.getCharHeight()) + + av.getRanges().getStartSeq(), + av .getAlignment().getHeight() - 1); if (seq < 0) { @@ -1423,34 +1427,15 @@ public class SeqPanel extends Panel implements MouseMotionListener, stretchGroup = av.getSelectionGroup(); - if (stretchGroup == null) + if (stretchGroup == null || !stretchGroup.contains(sequence, res)) { stretchGroup = av.getAlignment().findGroup(sequence, res); - av.setSelectionGroup(stretchGroup); - } - - if (stretchGroup == null - || !stretchGroup.getSequences(null).contains(sequence) - || stretchGroup.getStartRes() > res - || stretchGroup.getEndRes() < res) - { - stretchGroup = null; - - SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence); - - if (allGroups != null) + if (stretchGroup != null) { - for (int i = 0; i < allGroups.length; i++) - { - if (allGroups[i].getStartRes() <= res - && allGroups[i].getEndRes() >= res) - { - stretchGroup = allGroups[i]; - break; - } - } + // only update the current selection if the popup menu has a group to + // focus on + av.setSelectionGroup(stretchGroup); } - av.setSelectionGroup(stretchGroup); } // DETECT RIGHT MOUSE BUTTON IN AWT @@ -1662,8 +1647,10 @@ public class SeqPanel extends Panel implements MouseMotionListener, oldSeq = -1; } - if (res > av.endRes || res < av.startRes || y < av.startSeq - || y > av.endSeq) + if (res > av.getRanges().getEndRes() + || res < av.getRanges().getStartRes() + || y < av.getRanges().getStartSeq() + || y > av.getRanges().getEndSeq()) { mouseExited(evt); } @@ -1761,13 +1748,15 @@ public class SeqPanel extends Panel implements MouseMotionListener, if (evt != null) { - if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0) + if (mouseDragging && evt.getY() < 0 + && av.getRanges().getStartSeq() > 0) { running = ap.scrollUp(true); } if (mouseDragging && evt.getY() >= getSize().height - && av.getAlignment().getHeight() > av.getEndSeq()) + && av.getAlignment().getHeight() > av.getRanges() + .getEndSeq()) { running = ap.scrollUp(false); } @@ -1909,8 +1898,8 @@ public class SeqPanel extends Panel implements MouseMotionListener, public void scrollTo(int row, int column) { - row = row < 0 ? ap.av.startSeq : row; - column = column < 0 ? ap.av.startRes : column; + row = row < 0 ? ap.av.getRanges().getStartSeq() : row; + column = column < 0 ? ap.av.getRanges().getStartRes() : column; ap.scrollTo(column, column, row, true, true); } @@ -1922,8 +1911,9 @@ public class SeqPanel extends Panel implements MouseMotionListener, public void scrollToRow(int row) { - row = row < 0 ? ap.av.startSeq : row; - ap.scrollTo(ap.av.startRes, ap.av.startRes, row, true, true); + row = row < 0 ? ap.av.getRanges().getStartSeq() : row; + ap.scrollTo(ap.av.getRanges().getStartRes(), ap.av.getRanges() + .getStartRes(), row, true, true); } /** @@ -1934,8 +1924,8 @@ public class SeqPanel extends Panel implements MouseMotionListener, public void scrollToColumn(int column) { - column = column < 0 ? ap.av.startRes : column; - ap.scrollTo(column, column, ap.av.startSeq, true, true); + column = column < 0 ? ap.av.getRanges().getStartRes() : column; + ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true, true); } /** diff --git a/src/jalview/appletgui/SequenceRenderer.java b/src/jalview/appletgui/SequenceRenderer.java index 86d1f98..78ed4a3 100755 --- a/src/jalview/appletgui/SequenceRenderer.java +++ b/src/jalview/appletgui/SequenceRenderer.java @@ -20,10 +20,10 @@ */ package jalview.appletgui; -import jalview.api.FeatureRenderer; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.ResidueShaderI; +import jalview.renderer.seqfeatures.FeatureColourFinder; import java.awt.Color; import java.awt.Font; @@ -69,8 +69,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer this.renderGaps = renderGaps; } - @Override - public Color getResidueBoxColour(SequenceI seq, int i) + protected Color getResidueBoxColour(SequenceI seq, int i) { allGroups = av.getAlignment().findAllGroups(seq); @@ -96,20 +95,20 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer * * @param seq * @param position - * @param fr + * @param finder * @return */ @Override public Color getResidueColour(final SequenceI seq, int position, - FeatureRenderer fr) + FeatureColourFinder finder) { // TODO replace 8 or so code duplications with calls to this method // (refactored as needed) Color col = getResidueBoxColour(seq, position); - if (fr != null) + if (finder != null) { - col = fr.findFeatureColour(col, seq, position); + col = finder.findFeatureColour(col, seq, position); } return col; } diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index 9363c23..da3cb92 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -22,9 +22,11 @@ package jalview.bin; import jalview.datamodel.PDBEntry; import jalview.gui.UserDefinedColours; +import jalview.schemes.ColourSchemeLoader; import jalview.schemes.ColourSchemes; import jalview.schemes.UserColourScheme; import jalview.structure.StructureImportSettings; +import jalview.urls.IdOrgSettings; import jalview.util.ColorUtils; import jalview.ws.dbsources.das.api.DasSourceRegistryI; import jalview.ws.dbsources.das.datamodel.DasSourceRegistry; @@ -127,6 +129,10 @@ import org.apache.log4j.SimpleLayout; *

    • SORT_ALIGNMENT (No sort|Id|Pairwise Identity)
    • *
    • SEQUENCE_LINKS list of name|URL pairs for opening a url with * $SEQUENCE_ID$
    • + *
    • STORED_LINKS list of name|url pairs which user has entered but are not + * currently used + *
    • DEFAULT_LINK name of single url to be used when user double clicks a + * sequence id (must be in SEQUENCE_LINKS or STORED_LINKS) *
    • GROUP_LINKS list of name|URL[|<separator>] tuples - see * jalview.utils.GroupURLLink for more info
    • *
    • DAS_REGISTRY_URL the registry to query
    • @@ -184,6 +190,8 @@ import org.apache.log4j.SimpleLayout; *
    • STRUCTURE_DISPLAY choose from JMOL (default) or CHIMERA for 3D structure * display
    • *
    • CHIMERA_PATH specify full path to Chimera program (if non-standard)
    • + *
    • ID_ORG_HOSTURL location of jalview service providing identifiers.org urls + *
    • * *
    * Deprecated settings: @@ -225,6 +233,9 @@ public class Cache public static final String DAS_ACTIVE_SOURCE = "DAS_ACTIVE_SOURCE"; + /** + * Sifts settings + */ public static final String DEFAULT_SIFTS_DOWNLOAD_DIR = System .getProperty("user.home") + File.separatorChar @@ -235,6 +246,12 @@ public class Cache private final static String DEFAULT_FAIL_SAFE_PID_THRESHOLD = "30"; /** + * Identifiers.org download settings + */ + private static final String ID_ORG_FILE = System.getProperty("user.home") + + File.separatorChar + ".identifiers.org.ids.json"; + + /** * Allowed values are PDB or mmCIF */ private final static String PDB_DOWNLOAD_FORMAT = PDBEntry.Type.MMCIF @@ -445,6 +462,10 @@ public class Cache "sifts_cache_threshold_in_days", DEFAULT_CACHE_THRESHOLD_IN_DAYS)); + IdOrgSettings.setUrl(getDefault("ID_ORG_HOSTURL", + "http://www.jalview.org/services/identifiers")); + IdOrgSettings.setDownloadLocation(ID_ORG_FILE); + System.out .println("Jalview Version: " + codeVersion + codeInstallation); @@ -1017,7 +1038,7 @@ public class Cache String file = st.nextToken(); try { - UserColourScheme ucs = ColourSchemes.loadColourScheme(file); + UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file); if (ucs != null) { if (coloursFound.length() > 0) diff --git a/src/jalview/datamodel/Alignment.java b/src/jalview/datamodel/Alignment.java index a6f2bf4..41488ea 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -185,14 +185,7 @@ public class Alignment implements AlignmentI return AlignmentUtils.getSequencesByName(this); } - /** - * DOCUMENT ME! - * - * @param i - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ + @Override public SequenceI getSequenceAt(int i) { @@ -206,6 +199,28 @@ public class Alignment implements AlignmentI return null; } + @Override + public SequenceI getSequenceAtAbsoluteIndex(int i) + { + SequenceI seq = null; + if (getHiddenSequences().getSize() > 0) + { + seq = getHiddenSequences().getHiddenSequence(i); + if (seq == null) + { + // didn't find the sequence in the hidden sequences, get it from the + // alignment + int index = getHiddenSequences().findIndexWithoutHiddenSeqs(i); + seq = getSequenceAt(index); + } + } + else + { + seq = getSequenceAt(i); + } + return seq; + } + /** * Adds a sequence to the alignment. Recalculates maxLength and size. Note * this currently does not recalculate whether or not the alignment is @@ -320,30 +335,21 @@ public class Alignment implements AlignmentI } } - /** - * DOCUMENT ME! - * - * @param s - * DOCUMENT ME! - */ @Override public void deleteSequence(SequenceI s) { - deleteSequence(findIndex(s)); + synchronized (sequences) + { + deleteSequence(findIndex(s)); + } } - /** - * DOCUMENT ME! - * - * @param i - * DOCUMENT ME! - */ @Override public void deleteSequence(int i) { - if (i > -1 && i < getHeight()) + synchronized (sequences) { - synchronized (sequences) + if (i > -1 && i < getHeight()) { sequences.remove(i); hiddenSequences.adjustHeightSequenceDeleted(i); @@ -351,6 +357,18 @@ public class Alignment implements AlignmentI } } + @Override + public void deleteHiddenSequence(int i) + { + synchronized (sequences) + { + if (i > -1 && i < getHeight()) + { + sequences.remove(i); + } + } + } + /* * (non-Javadoc) * @@ -668,22 +686,19 @@ public class Alignment implements AlignmentI return -1; } - /** - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ + @Override public int getHeight() { return sequences.size(); } - /** - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ + @Override + public int getAbsoluteHeight() + { + return sequences.size() + getHiddenSequences().getSize(); + } + @Override public int getWidth() { @@ -769,6 +784,12 @@ public class Alignment implements AlignmentI return true; } + @Override + public boolean isHidden(int alignmentIndex) + { + return (getHiddenSequences().getHiddenSequence(alignmentIndex) != null); + } + /** * Delete all annotations, including auto-calculated if the flag is set true. * Returns true if at least one annotation was deleted, else false. diff --git a/src/jalview/datamodel/AlignmentAnnotation.java b/src/jalview/datamodel/AlignmentAnnotation.java index 05688cb..bbd3ce4 100755 --- a/src/jalview/datamodel/AlignmentAnnotation.java +++ b/src/jalview/datamodel/AlignmentAnnotation.java @@ -170,10 +170,14 @@ public class AlignmentAnnotation */ private Map sequenceMapping; - /** DOCUMENT ME!! */ + /** + * lower range for quantitative data + */ public float graphMin; - /** DOCUMENT ME!! */ + /** + * Upper range for quantitative data + */ public float graphMax; /** diff --git a/src/jalview/datamodel/AlignmentI.java b/src/jalview/datamodel/AlignmentI.java index 752235b..2abb1f8 100755 --- a/src/jalview/datamodel/AlignmentI.java +++ b/src/jalview/datamodel/AlignmentI.java @@ -31,13 +31,22 @@ import java.util.Set; public interface AlignmentI extends AnnotatedCollectionI { /** - * Calculates the number of sequences in an alignment + * Calculates the number of sequences in an alignment, excluding hidden + * sequences * * @return Number of sequences in alignment */ int getHeight(); /** + * Calculates the number of sequences in an alignment, including hidden + * sequences + * + * @return Number of sequences in alignment + */ + int getAbsoluteHeight(); + + /** * * Calculates the maximum width of the alignment, including gaps. * @@ -65,6 +74,15 @@ public interface AlignmentI extends AnnotatedCollectionI boolean isAligned(boolean includeHidden); /** + * Answers if the sequence at alignmentIndex is hidden + * + * @param alignmentIndex + * the index to check + * @return true if the sequence is hidden + */ + boolean isHidden(int alignmentIndex); + + /** * Gets sequences as a Synchronized collection * * @return All sequences in alignment. @@ -90,6 +108,17 @@ public interface AlignmentI extends AnnotatedCollectionI SequenceI getSequenceAt(int i); /** + * Find a specific sequence in this alignment. + * + * @param i + * Index of required sequence in full alignment, i.e. if all columns + * were visible + * + * @return SequenceI at given index. + */ + SequenceI getSequenceAtAbsoluteIndex(int i); + + /** * Returns a map of lists of sequences keyed by sequence name. * * @return @@ -118,7 +147,9 @@ public interface AlignmentI extends AnnotatedCollectionI SequenceI replaceSequenceAt(int i, SequenceI seq); /** - * Deletes a sequence from the alignment + * Deletes a sequence from the alignment. Updates hidden sequences to account + * for the removed sequence. Do NOT use this method to delete sequences which + * are just hidden. * * @param s * Sequence to be deleted. @@ -126,7 +157,9 @@ public interface AlignmentI extends AnnotatedCollectionI void deleteSequence(SequenceI s); /** - * Deletes a sequence from the alignment. + * Deletes a sequence from the alignment. Updates hidden sequences to account + * for the removed sequence. Do NOT use this method to delete sequences which + * are just hidden. * * @param i * Index of sequence to be deleted. @@ -134,6 +167,14 @@ public interface AlignmentI extends AnnotatedCollectionI void deleteSequence(int i); /** + * Deletes a sequence in the alignment which has been hidden. + * + * @param i + * Index of sequence to be deleted + */ + void deleteHiddenSequence(int i); + + /** * Finds sequence in alignment using sequence name as query. * * @param name @@ -157,10 +198,11 @@ public interface AlignmentI extends AnnotatedCollectionI /** * Returns the first group (in the order in which groups were added) that - * includes the given sequence and aligned position (base 0), or null if none - * found + * includes the given sequence instance and aligned position (base 0), or null + * if none found * * @param seq + * - must be contained in the alignment (not a dataset sequence) * @param position * * @return @@ -544,4 +586,5 @@ public interface AlignmentI extends AnnotatedCollectionI * @return */ public int[] getVisibleStartAndEndIndex(List hiddenCols); + } diff --git a/src/jalview/datamodel/BinarySequence.java b/src/jalview/datamodel/BinarySequence.java index ed57dce..62ee974 100755 --- a/src/jalview/datamodel/BinarySequence.java +++ b/src/jalview/datamodel/BinarySequence.java @@ -69,13 +69,9 @@ public class BinarySequence extends Sequence { int nores = (isNa) ? ResidueProperties.maxNucleotideIndex : ResidueProperties.maxProteinIndex; - // Set all matrix to 0 + dbinary = new double[getSequence().length * nores]; - for (int i = 0; i < dbinary.length; i++) - { - dbinary[i] = 0.0; - } return nores; } @@ -134,14 +130,8 @@ public class BinarySequence extends Sequence private void matrixEncode(final int[] aaIndex, final int[][] matrix) { - // Set all matrix to 0 - // dbinary = new double[getSequence().length * 21]; - int nores = initMatrixGetNoRes(); - // for (int i = 0; i < dbinary.length; i++) { - // dbinary[i] = 0.0; - // } for (int i = 0, iSize = getSequence().length; i < iSize; i++) { int aanum = nores - 1; diff --git a/src/jalview/datamodel/ColumnSelection.java b/src/jalview/datamodel/ColumnSelection.java index 98a7fe2..97bc5a3 100644 --- a/src/jalview/datamodel/ColumnSelection.java +++ b/src/jalview/datamodel/ColumnSelection.java @@ -690,8 +690,8 @@ public class ColumnSelection * left-most visible column will always be returned. * * @param hiddenColumn - * int - * @return int + * the column index in the full alignment including hidden columns + * @return the position of the column in the visible alignment */ public int findColumnPosition(int hiddenColumn) { @@ -708,15 +708,89 @@ public class ColumnSelection result -= region[1] + 1 - region[0]; } } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size())); - if (hiddenColumn > region[0] && hiddenColumn < region[1]) - { - return region[0] + hiddenColumn - result; + + if (hiddenColumn >= region[0] && hiddenColumn <= region[1]) + { + // Here the hidden column is within a region, so + // we want to return the position of region[0]-1, adjusted for any + // earlier hidden columns. + // Calculate the difference between the actual hidden col position + // and region[0]-1, and then subtract from result to convert result from + // the adjusted hiddenColumn value to the adjusted region[0]-1 value + + // However, if the region begins at 0 we cannot return region[0]-1 + // just return 0 + if (region[0] == 0) + { + return 0; + } + else + { + return result - (hiddenColumn - region[0] + 1); + } } } return result; // return the shifted position after removing hidden columns. } /** + * Find the visible column which is a given visible number of columns to the + * left of another visible column. i.e. for a startColumn x, the column which + * is distance 1 away will be column x-1. + * + * @param visibleDistance + * the number of visible columns to offset by + * @param startColumn + * the column to start from + * @return the position of the column in the visible alignment + */ + public int subtractVisibleColumns(int visibleDistance, int startColumn) + { + int distance = visibleDistance; + + // in case startColumn is in a hidden region, move it to the left + int start = adjustForHiddenColumns(findColumnPosition(startColumn)); + + // get index of hidden region to left of start + int index = getHiddenIndexLeft(start); + if (index == -1) + { + // no hidden regions to left of startColumn + return start - distance; + } + + // walk backwards through the alignment subtracting the counts of visible + // columns from distance + int[] region; + int gap = 0; + int nextstart = start; + + while ((index > -1) && (distance - gap > 0)) + { + // subtract the gap to right of region from distance + distance -= gap; + start = nextstart; + + // calculate the next gap + region = hiddenColumns.get(index); + gap = start - region[1]; + + // set start to just to left of current region + nextstart = region[0] - 1; + index--; + } + + if (distance - gap > 0) + { + // fell out of loop because there are no more hidden regions + distance -= gap; + return nextstart - distance; + } + return start - distance; + + } + + /** * Use this method to determine where the next hiddenRegion starts * * @param hiddenRegion @@ -805,6 +879,35 @@ public class ColumnSelection } + /** + * This method returns the index of the hidden region to the left of a column + * position. If the column is in a hidden region it returns the index of the + * region to the left. If there is no hidden region to the left it returns -1. + * + * @param pos + * int + */ + private int getHiddenIndexLeft(int pos) + { + if (hiddenColumns != null) + { + int index = hiddenColumns.size() - 1; + do + { + int[] region = hiddenColumns.elementAt(index); + if (pos > region[1]) + { + return index; + } + + index--; + } while (index > -1); + } + + return -1; + + } + public void hideSelectedColumns() { synchronized (selection) diff --git a/src/jalview/datamodel/HiddenSequences.java b/src/jalview/datamodel/HiddenSequences.java index 9e2cf72..6950c28 100755 --- a/src/jalview/datamodel/HiddenSequences.java +++ b/src/jalview/datamodel/HiddenSequences.java @@ -154,8 +154,8 @@ public class HiddenSequences hiddenSequences = new SequenceI[alignment.getHeight()]; } - int alignmentIndex = alignment.findIndex(sequence); - alignmentIndex = adjustForHiddenSeqs(alignmentIndex); + int absAlignmentIndex = alignment.findIndex(sequence); + int alignmentIndex = adjustForHiddenSeqs(absAlignmentIndex); if (hiddenSequences[alignmentIndex] != null) { @@ -164,7 +164,7 @@ public class HiddenSequences hiddenSequences[alignmentIndex] = sequence; - alignment.deleteSequence(sequence); + alignment.deleteHiddenSequence(absAlignmentIndex); } public List showAll( @@ -246,6 +246,12 @@ public class HiddenSequences return hiddenSequences == null ? null : hiddenSequences[alignmentIndex]; } + /** + * Convert absolute alignment index to visible alignment index + * + * @param alignmentIndex + * @return + */ public int findIndexWithoutHiddenSeqs(int alignmentIndex) { if (hiddenSequences == null) @@ -254,8 +260,14 @@ public class HiddenSequences } int index = 0; int hiddenSeqs = 0; + int diff = 0; if (hiddenSequences.length <= alignmentIndex) { + // if the alignmentIndex runs past the end of hidden sequences + // and therefore actually past the end of the alignment + // store the difference to add back on at the end, so that behaviour + // is consistent with hidden columns behaviour (used by overview panel) + diff = alignmentIndex - hiddenSequences.length + 1; alignmentIndex = hiddenSequences.length - 1; } @@ -268,9 +280,50 @@ public class HiddenSequences index++; } - return (alignmentIndex - hiddenSeqs); + return (alignmentIndex - hiddenSeqs + diff); + } + + /** + * Find the visible row which is a given visible number of rows above another + * visible row. i.e. for a startRow x, the row which is distance 1 away will + * be row x-1. + * + * @param visibleDistance + * the number of visible rows to offset by + * @param startRow + * the row to start from + * @return the position of the row in the visible alignment + */ + public int subtractVisibleRows(int visibleDistance, int startRow) + { + // walk upwards through the alignment + // count all the non-null sequences until we have visibleDistance counted + // then return the next visible sequence + if (hiddenSequences == null) + { + return startRow - visibleDistance; + } + + int index = startRow; + int count = 0; + while ((index > -1) && (count < visibleDistance)) + { + if (hiddenSequences[index] == null) + { + // count visible sequences + count++; + } + index--; + } + return index; } + /** + * Convert alignment index from visible alignment to absolute alignment + * + * @param alignmentIndex + * @return + */ public int adjustForHiddenSeqs(int alignmentIndex) { if (hiddenSequences == null) diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index c8b94ce..b0faf21 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -314,12 +314,11 @@ public class Sequence extends ASequence implements SequenceI } @Override - public synchronized void addSequenceFeature(SequenceFeature sf) + public synchronized boolean addSequenceFeature(SequenceFeature sf) { if (sequenceFeatures == null && datasetSequence != null) { - datasetSequence.addSequenceFeature(sf); - return; + return datasetSequence.addSequenceFeature(sf); } if (sequenceFeatures == null) { @@ -330,7 +329,7 @@ public class Sequence extends ASequence implements SequenceI { if (sequenceFeatures[i].equals(sf)) { - return; + return false; } } @@ -339,6 +338,7 @@ public class Sequence extends ASequence implements SequenceI temp[sequenceFeatures.length] = sf; sequenceFeatures = temp; + return true; } @Override diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index 4f9d530..e37c55e 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -1411,4 +1411,24 @@ public class SequenceGroup implements AnnotatedCollectionI } return false; } + + /** + * @param seq + * @return true if seq is a member of the group + */ + + public boolean contains(SequenceI seq1) + { + return sequences.contains(seq1); + } + + /** + * @param seq + * @param apos + * @return true if startRes<=apos and endRes>=apos and seq is in the group + */ + public boolean contains(SequenceI seq, int apos) + { + return (startRes <= apos && endRes >= apos) && sequences.contains(seq); + } } diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index aec68ab..92f797f 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -337,7 +337,14 @@ public interface SequenceI extends ASequenceI */ public void addDBRef(DBRefEntry entry); - public void addSequenceFeature(SequenceFeature sf); + /** + * Adds the given sequence feature and returns true, or returns false if it is + * already present on the sequence + * + * @param sf + * @return + */ + public boolean addSequenceFeature(SequenceFeature sf); public void deleteFeature(SequenceFeature sf); diff --git a/src/jalview/ext/android/ContainerHelpers.java b/src/jalview/ext/android/ContainerHelpers.java index 4033dcc..26bd142 100644 --- a/src/jalview/ext/android/ContainerHelpers.java +++ b/src/jalview/ext/android/ContainerHelpers.java @@ -19,8 +19,10 @@ package jalview.ext.android; /* * Copied to Jalview September 2016. * Only the members of this class required for SparseIntArray were copied. - * Method binarySearch(short[] array, int size, short value) added to support + * Change Log: + * Sep 2016: Method binarySearch(short[] array, int size, short value) added to support * SparseShortArray. + * Jan 2017: EMPTY_DOUBLES added */ class ContainerHelpers { @@ -28,6 +30,8 @@ class ContainerHelpers static final int[] EMPTY_INTS = new int[0]; + static final double[] EMPTY_DOUBLES = new double[0]; + static final long[] EMPTY_LONGS = new long[0]; static final Object[] EMPTY_OBJECTS = new Object[0]; diff --git a/src/jalview/ext/android/SparseDoubleArray.java b/src/jalview/ext/android/SparseDoubleArray.java new file mode 100644 index 0000000..eaf059c --- /dev/null +++ b/src/jalview/ext/android/SparseDoubleArray.java @@ -0,0 +1,443 @@ +package jalview.ext.android; + +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * SparseDoubleArray map integers to doubles. Unlike a normal array of integers, + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integer to Double, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra + * entry object for each mapping. + * + *

    + * Note that this container keeps its mappings in an array data structure, using + * a binary search to find keys. The implementation is not intended to be + * appropriate for data structures that may contain large numbers of items. It + * is generally slower than a traditional HashMap, since lookups require a + * binary search and adds and removes require inserting and deleting entries in + * the array. For containers holding up to hundreds of items, the performance + * difference is not significant, less than 50%. + *

    + * + *

    + * It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int). + *

    + */ + +/* + * Change log: + * Jan 2017 cloned from SparseIntArray for Jalview to support SparseMatrix + * - SparseDoubleArray(double[]) constructor added + * - multiply() added for more efficient multiply (or divide) of a value + */ +public class SparseDoubleArray implements Cloneable +{ + private int[] mKeys; + + private double[] mValues; + + private int mSize; + + /** + * Creates a new SparseDoubleArray containing no mappings. + */ + public SparseDoubleArray() + { + this(10); + } + + /** + * Creates a new SparseDoubleArray containing no mappings that will not + * require any additional memory allocation to store the specified number of + * mappings. If you supply an initial capacity of 0, the sparse array will be + * initialized with a light-weight representation not requiring any additional + * array allocations. + */ + public SparseDoubleArray(int initialCapacity) + { + if (initialCapacity == 0) + { + mKeys = ContainerHelpers.EMPTY_INTS; + mValues = ContainerHelpers.EMPTY_DOUBLES; + } + else + { + initialCapacity = idealDoubleArraySize(initialCapacity); + mKeys = new int[initialCapacity]; + mValues = new double[initialCapacity]; + } + mSize = 0; + } + + /** + * Constructor given an array of double values; stores the non-zero values + * + * @param row + */ + public SparseDoubleArray(double[] row) + { + this(); + for (int i = 0; i < row.length; i++) + { + if (row[i] != 0d) + { + put(i, row[i]); + } + } + } + + @Override + public SparseDoubleArray clone() + { + SparseDoubleArray clone = null; + try + { + clone = (SparseDoubleArray) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException cnse) + { + /* ignore */ + } + return clone; + } + + /** + * Gets the value mapped from the specified key, or 0 if no such + * mapping has been made. + */ + public double get(int key) + { + return get(key, 0d); + } + + /** + * Gets the int mapped from the specified key, or the specified value if no + * such mapping has been made. + */ + public double get(int key, double valueIfKeyNotFound) + { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + if (i < 0) + { + return valueIfKeyNotFound; + } + else + { + return mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(int key) + { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + if (i >= 0) + { + removeAt(i); + } + } + + /** + * Removes the mapping at the given index. + */ + public void removeAt(int index) + { + System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); + System.arraycopy(mValues, index + 1, mValues, index, mSize + - (index + 1)); + mSize--; + } + + /** + * Adds a mapping from the specified key to the specified value, replacing the + * previous mapping from the specified key if there was one. + */ + public void put(int key, double value) + { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + if (i >= 0) + { + mValues[i] = value; + } + else + { + i = ~i; + if (mSize >= mKeys.length) + { + int n = idealDoubleArraySize(mSize + 1); + int[] nkeys = new int[n]; + double[] nvalues = new double[n]; + // Log.e("SparseDoubleArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + mKeys = nkeys; + mValues = nvalues; + } + if (mSize - i != 0) + { + // Log.e("SparseDoubleArray", "move " + (mSize - i)); + System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); + System.arraycopy(mValues, i, mValues, i + 1, mSize - i); + } + mKeys[i] = key; + mValues[i] = value; + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseDoubleArray + * currently stores. + */ + public int size() + { + return mSize; + } + + /** + * Given an index in the range 0...size()-1, returns the key from + * the indexth key-value mapping that this SparseDoubleArray + * stores. + * + *

    + * The keys corresponding to indices in ascending order are guaranteed to be + * in ascending order, e.g., keyAt(0) will return the smallest + * key and keyAt(size()-1) will return the largest key. + *

    + */ + public int keyAt(int index) + { + return mKeys[index]; + } + + /** + * Given an index in the range 0...size()-1, returns the value + * from the indexth key-value mapping that this SparseDoubleArray + * stores. + * + *

    + * The values corresponding to indices in ascending order are guaranteed to be + * associated with keys in ascending order, e.g., valueAt(0) will + * return the value associated with the smallest key and + * valueAt(size()-1) will return the value associated with the + * largest key. + *

    + */ + public double valueAt(int index) + { + return mValues[index]; + } + + /** + * Returns the index for which {@link #keyAt} would return the specified key, + * or a negative number if the specified key is not mapped. + */ + public int indexOfKey(int key) + { + return ContainerHelpers.binarySearch(mKeys, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the specified key, + * or a negative number if no keys map to the specified value. Beware that + * this is a linear search, unlike lookups by key, and that multiple keys can + * map to the same value and this will find only one of them. + */ + public int indexOfValue(double value) + { + for (int i = 0; i < mSize; i++) + { + if (mValues[i] == value) + { + return i; + } + } + return -1; + } + + /** + * Removes all key-value mappings from this SparseDoubleArray. + */ + public void clear() + { + mSize = 0; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where the key + * is greater than all existing keys in the array. + */ + public void append(int key, double value) + { + if (mSize != 0 && key <= mKeys[mSize - 1]) + { + put(key, value); + return; + } + int pos = mSize; + if (pos >= mKeys.length) + { + int n = idealDoubleArraySize(pos + 1); + int[] nkeys = new int[n]; + double[] nvalues = new double[n]; + // Log.e("SparseDoubleArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + mKeys = nkeys; + mValues = nvalues; + } + mKeys[pos] = key; + mValues[pos] = value; + mSize = pos + 1; + } + + /** + * Created by analogy with + * com.android.internal.util.ArrayUtils#idealLongArraySize + * + * @param i + * @return + */ + public static int idealDoubleArraySize(int need) + { + return idealByteArraySize(need * 8) / 8; + } + + /** + * Inlined here by copying from com.android.internal.util.ArrayUtils + * + * @param i + * @return + */ + public static int idealByteArraySize(int need) + { + for (int i = 4; i < 32; i++) + { + if (need <= (1 << i) - 12) + { + return (1 << i) - 12; + } + } + + return need; + } + + /** + * {@inheritDoc} + * + *

    + * This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() + { + if (size() <= 0) + { + return "{}"; + } + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i = 0; i < mSize; i++) + { + if (i > 0) + { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + double value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); + } + + /** + * Method (copied from put) added for Jalview to efficiently increment a key's + * value if present, else add it with the given value. This avoids a double + * binary search (once to get the value, again to put the updated value). + * + * @param key + * @oparam toAdd + * @return the new value for the key + */ + public double add(int key, double toAdd) + { + double newValue = toAdd; + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + if (i >= 0) + { + mValues[i] += toAdd; + newValue = mValues[i]; + } + else + { + i = ~i; + if (mSize >= mKeys.length) + { + int n = idealDoubleArraySize(mSize + 1); + int[] nkeys = new int[n]; + double[] nvalues = new double[n]; + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + mKeys = nkeys; + mValues = nvalues; + } + if (mSize - i != 0) + { + System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); + System.arraycopy(mValues, i, mValues, i + 1, mSize - i); + } + mKeys[i] = key; + mValues[i] = toAdd; + mSize++; + } + return newValue; + } + + /** + * Method added for Jalview to efficiently multiply a key's value if present, + * else do nothing. This avoids a double binary search (once to get the value, + * again to put the updated value). + * + * @param key + * @oparam toAdd + * @return the new value for the key + */ + public double divide(int key, double divisor) + { + double newValue = 0d; + if (divisor == 0d) + { + return newValue; + } + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + if (i >= 0) + { + mValues[i] /= divisor; + newValue = mValues[i]; + } + return newValue; + } +} diff --git a/src/jalview/ext/ensembl/EnsemblSeqProxy.java b/src/jalview/ext/ensembl/EnsemblSeqProxy.java index 732bc0f..233707b 100644 --- a/src/jalview/ext/ensembl/EnsemblSeqProxy.java +++ b/src/jalview/ext/ensembl/EnsemblSeqProxy.java @@ -38,6 +38,7 @@ import jalview.io.gff.SequenceOntologyI; import jalview.util.Comparison; import jalview.util.DBRefUtils; import jalview.util.MapList; +import jalview.util.RangeComparator; import java.io.IOException; import java.net.MalformedURLException; @@ -110,26 +111,6 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient } /** - * A comparator to sort ranges into ascending start position order - */ - private class RangeSorter implements Comparator - { - boolean forwards; - - RangeSorter(boolean forward) - { - forwards = forward; - } - - @Override - public int compare(int[] o1, int[] o2) - { - return (forwards ? 1 : -1) * Integer.compare(o1[0], o2[0]); - } - - } - - /** * Default constructor (to use rest.ensembl.org) */ public EnsemblSeqProxy() @@ -626,7 +607,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient * a final sort is needed since Ensembl returns CDS sorted within source * (havana / ensembl_havana) */ - Collections.sort(regions, new RangeSorter(direction == 1)); + Collections.sort(regions, new RangeComparator(direction == 1)); List to = Arrays.asList(new int[] { start, start + mappedLength - 1 }); diff --git a/src/jalview/ext/jmol/JalviewJmolBinding.java b/src/jalview/ext/jmol/JalviewJmolBinding.java index bf80831..94df99a 100644 --- a/src/jalview/ext/jmol/JalviewJmolBinding.java +++ b/src/jalview/ext/jmol/JalviewJmolBinding.java @@ -20,6 +20,7 @@ */ package jalview.ext.jmol; +import jalview.api.AlignmentViewPanel; import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.datamodel.AlignmentI; @@ -34,6 +35,7 @@ import jalview.structure.AtomSpec; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; import jalview.structures.models.AAStructureBindingModel; +import jalview.util.MessageManager; import java.awt.Color; import java.awt.Container; @@ -43,6 +45,7 @@ import java.io.File; import java.net.URL; import java.security.AccessControlException; import java.util.ArrayList; +import java.util.BitSet; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -227,21 +230,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } /** - * Construct and send a command to align structures against a reference - * structure, based on one or more sequence alignments - * - * @param _alignment - * an array of alignments to process - * @param _refStructure - * an array of corresponding reference structures (index into pdb - * file array); if a negative value is passed, the first PDB file - * mapped to an alignment sequence is used as the reference for - * superposition - * @param _hiddenCols - * an array of corresponding hidden columns for each alignment + * {@inheritDoc} */ @Override - public void superposeStructures(AlignmentI[] _alignment, + public String superposeStructures(AlignmentI[] _alignment, int[] _refStructure, ColumnSelection[] _hiddenCols) { while (viewer.isScriptExecuting()) @@ -261,7 +253,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel String[] files = getPdbFile(); if (!waitForFileLoad(files)) { - return; + return null; } StringBuilder selectioncom = new StringBuilder(256); @@ -277,6 +269,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel nSeconds = " " + (2.0 / files.length) + " "; // if (nSeconds).substring(0,5)+" "; } + // see JAL-1345 - should really automatically turn off the animation for // large numbers of structures, but Jmol doesn't seem to allow that. // nSeconds = " "; @@ -302,14 +295,16 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } /* - * 'matched' array will hold 'true' for visible alignment columns where + * 'matched' bit j will be set for visible alignment columns j where * all sequences have a residue with a mapping to the PDB structure */ - // TODO could use a BitSet for matched - boolean matched[] = new boolean[alignment.getWidth()]; - for (int m = 0; m < matched.length; m++) + BitSet matched = new BitSet(); + for (int m = 0; m < alignment.getWidth(); m++) { - matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true; + if (hiddenCols == null || hiddenCols.isVisible(m)) + { + matched.set(m); + } } SuperposeData[] structures = new SuperposeData[files.length]; @@ -334,17 +329,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } String[] selcom = new String[files.length]; - int nmatched = 0; - for (boolean b : matched) - { - if (b) - { - nmatched++; - } - } + int nmatched = matched.cardinality(); if (nmatched < 4) { - // TODO: bail out here because superposition illdefined? + return (MessageManager.formatMessage( +"label.insufficient_residues", + nmatched)); } /* @@ -359,35 +349,35 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel boolean run = false; StringBuilder molsel = new StringBuilder(); molsel.append("{"); - for (int r = 0; r < matched.length; r++) + + int nextColumnMatch = matched.nextSetBit(0); + while (nextColumnMatch != -1) { - if (matched[r]) + int pdbResNo = structures[pdbfnum].pdbResNo[nextColumnMatch]; + if (lpos != pdbResNo - 1) { - int pdbResNo = structures[pdbfnum].pdbResNo[r]; - if (lpos != pdbResNo - 1) + // discontinuity + if (lpos != -1) { - // discontinuity - if (lpos != -1) - { - molsel.append(lpos); - molsel.append(chainCd); - molsel.append("|"); - } - run = false; + molsel.append(lpos); + molsel.append(chainCd); + molsel.append("|"); } - else + run = false; + } + else + { + // continuous run - and lpos >-1 + if (!run) { - // continuous run - and lpos >-1 - if (!run) - { - // at the beginning, so add dash - molsel.append(lpos); - molsel.append("-"); - } - run = true; + // at the beginning, so add dash + molsel.append(lpos); + molsel.append("-"); } - lpos = pdbResNo; + run = true; } + lpos = pdbResNo; + nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1); } /* * add final selection phrase @@ -473,6 +463,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel + selectioncom.toString() + "); cartoons; "); // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString()); } + + return null; } public void evalStateCommand(String command) @@ -507,17 +499,15 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel /** * @param files * @param sr - * @param fr - * @param alignment + * @param viewPanel * @return */ @Override protected StructureMappingcommandSet[] getColourBySequenceCommands( - String[] files, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel) { return JmolCommands.getColourBySequenceCommand(getSsm(), files, - getSequence(), sr, fr, alignment); + getSequence(), sr, viewPanel); } /** diff --git a/src/jalview/ext/jmol/JmolCommands.java b/src/jalview/ext/jmol/JmolCommands.java index d5676c5..4212749 100644 --- a/src/jalview/ext/jmol/JmolCommands.java +++ b/src/jalview/ext/jmol/JmolCommands.java @@ -20,16 +20,21 @@ */ package jalview.ext.jmol; +import jalview.api.AlignViewportI; +import jalview.api.AlignmentViewPanel; import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.datamodel.AlignmentI; +import jalview.datamodel.ColumnSelection; import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; import java.awt.Color; import java.util.ArrayList; +import java.util.List; /** * Routines for generating Jmol commands for Jalview/Jmol binding another @@ -50,11 +55,15 @@ public class JmolCommands */ public static StructureMappingcommandSet[] getColourBySequenceCommand( StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + SequenceI[][] sequence, SequenceRenderer sr, + AlignmentViewPanel viewPanel) { - - ArrayList cset = new ArrayList(); + FeatureRenderer fr = viewPanel.getFeatureRenderer(); + FeatureColourFinder finder = new FeatureColourFinder(fr); + AlignViewportI viewport = viewPanel.getAlignViewport(); + ColumnSelection cs = viewport.getColumnSelection(); + AlignmentI al = viewport.getAlignment(); + List cset = new ArrayList(); for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { @@ -74,9 +83,9 @@ public class JmolCommands for (int sp, m = 0; m < mapping.length; m++) { if (mapping[m].getSequence() == sequence[pdbfnum][s] - && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1) + && (sp = al.findIndex(sequence[pdbfnum][s])) > -1) { - SequenceI asp = alignment.getSequenceAt(sp); + SequenceI asp = al.getSequenceAt(sp); for (int r = 0; r < asp.getLength(); r++) { // no mapping to gaps in sequence @@ -93,12 +102,18 @@ public class JmolCommands lastPos = pos; - Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r); + Color col = sr.getResidueColour(sequence[pdbfnum][s], r, + finder); - if (fr != null) + /* + * shade hidden regions darker + */ + if (!cs.isVisible(r)) { - col = fr.findFeatureColour(col, sequence[pdbfnum][s], r); + // col = ColorUtils.darkerThan(col); + col = Color.GRAY; } + String newSelcom = (mapping[m].getChain() != " " ? ":" + mapping[m].getChain() : "") + "/" diff --git a/src/jalview/ext/rbvi/chimera/AtomSpecModel.java b/src/jalview/ext/rbvi/chimera/AtomSpecModel.java new file mode 100644 index 0000000..d62cc3c --- /dev/null +++ b/src/jalview/ext/rbvi/chimera/AtomSpecModel.java @@ -0,0 +1,180 @@ +package jalview.ext.rbvi.chimera; + +import jalview.util.RangeComparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * A class to model a Chimera atomspec pattern, for example + * + *

    + * #0:15.A,28.A,54.A,63.A,70-72.A,83-84.A,97-98.A|#1:2.A,6.A,11.A,13-14.A,70.A,82.A,96-97.A
    + * 
    + * + * where + *
      + *
    • #0 is a model number
    • + *
    • 15 or 70-72 is a residue number, or range of residue numbers
    • + *
    • .A is a chain identifier
    • + *
    • residue ranges are separated by comma
    • + *
    • atomspecs for distinct models are separated by | (or)
    • + *
    + * + *
    + * @see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
    + * 
    + */ +public class AtomSpecModel +{ + private Map>> atomSpec; + + /** + * Constructor + */ + public AtomSpecModel() + { + atomSpec = new TreeMap>>(); + } + + /** + * Adds one contiguous range to this atom spec + * + * @param model + * @param startPos + * @param endPos + * @param chain + */ + public void addRange(int model, int startPos, int endPos, String chain) + { + /* + * Get/initialize map of data for the colour and model + */ + Map> modelData = atomSpec.get(model); + if (modelData == null) + { + atomSpec.put(model, modelData = new TreeMap>()); + } + + /* + * Get/initialize map of data for colour, model and chain + */ + List chainData = modelData.get(chain); + if (chainData == null) + { + chainData = new ArrayList(); + modelData.put(chain, chainData); + } + + /* + * Add the start/end positions + */ + chainData.add(new int[] { startPos, endPos }); + // TODO add intelligently, using a RangeList class + } + + /** + * Returns the range(s) formatted as a Chimera atomspec + * + * @return + */ + public String getAtomSpec() + { + StringBuilder sb = new StringBuilder(128); + boolean firstModel = true; + for (Integer model : atomSpec.keySet()) + { + if (!firstModel) + { + sb.append("|"); + } + firstModel = false; + sb.append("#").append(model).append(":"); + + boolean firstPositionForModel = true; + final Map> modelData = atomSpec.get(model); + + for (String chain : modelData.keySet()) + { + chain = chain.trim(); + + List rangeList = modelData.get(chain); + + /* + * sort ranges into ascending start position order + */ + Collections.sort(rangeList, new RangeComparator(true)); + + int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0]; + int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1]; + + Iterator iterator = rangeList.iterator(); + while (iterator.hasNext()) + { + int[] range = iterator.next(); + if (range[0] <= end + 1) + { + /* + * range overlaps or is contiguous with the last one + * - so just extend the end position, and carry on + * (unless this is the last in the list) + */ + end = Math.max(end, range[1]); + } + else + { + /* + * we have a break so append the last range + */ + appendRange(sb, start, end, chain, firstPositionForModel); + firstPositionForModel = false; + start = range[0]; + end = range[1]; + } + } + + /* + * and append the last range + */ + if (!rangeList.isEmpty()) + { + appendRange(sb, start, end, chain, firstPositionForModel); + firstPositionForModel = false; + } + } + } + return sb.toString(); + } + + /** + * @param sb + * @param start + * @param end + * @param chain + * @param firstPositionForModel + */ + protected void appendRange(StringBuilder sb, int start, int end, + String chain, boolean firstPositionForModel) + { + if (!firstPositionForModel) + { + sb.append(","); + } + if (end == start) + { + sb.append(start); + } + else + { + sb.append(start).append("-").append(end); + } + if (chain.length() > 0) + { + sb.append(".").append(chain); + } + } +} diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 8f6b2f1..1d8b944 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -20,10 +20,15 @@ */ package jalview.ext.rbvi.chimera; +import jalview.api.AlignViewportI; +import jalview.api.AlignmentViewPanel; import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.datamodel.AlignmentI; +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; @@ -32,11 +37,10 @@ import jalview.util.Comparison; import java.awt.Color; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; /** * Routines for generating Chimera commands for Jalview/Chimera binding @@ -47,26 +51,32 @@ import java.util.TreeMap; public class ChimeraCommands { + public static final String NAMESPACE_PREFIX = "jv_"; + /** - * utility to construct the commands to colour chains by the given alignment - * for passing to Chimera - * - * @returns Object[] { Object[] { , + * Constructs Chimera commands to colour residues as per the Jalview alignment * + * @param ssm + * @param files + * @param sequence + * @param sr + * @param fr + * @param viewPanel + * @return */ public static StructureMappingcommandSet[] getColourBySequenceCommand( StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + SequenceI[][] sequence, SequenceRenderer sr, + AlignmentViewPanel viewPanel) { - Map>>> colourMap = buildColoursMap( - ssm, files, sequence, sr, fr, alignment); + Map colourMap = buildColoursMap(ssm, files, + sequence, sr, viewPanel); List colourCommands = buildColourCommands(colourMap); StructureMappingcommandSet cs = new StructureMappingcommandSet( ChimeraCommands.class, null, - colourCommands.toArray(new String[0])); + colourCommands.toArray(new String[colourCommands.size()])); return new StructureMappingcommandSet[] { cs }; } @@ -76,18 +86,18 @@ public class ChimeraCommands * 'color' commands (one per distinct colour used). The format of each command * is * - *
    color colorname #modelnumber:range.chain e.g. color #00ff00 - * #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... - * - * @see http - * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec - * .html + *
    +   * 
    + * color colorname #modelnumber:range.chain + * e.g. color #00ff00 #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... + *
    + *
    * * @param colourMap * @return */ protected static List buildColourCommands( - Map>>> colourMap) + Map colourMap) { /* * This version concatenates all commands into a single String (semi-colon @@ -97,8 +107,9 @@ public class ChimeraCommands List commands = new ArrayList(); StringBuilder sb = new StringBuilder(256); boolean firstColour = true; - for (Color colour : colourMap.keySet()) + for (Object key : colourMap.keySet()) { + Color colour = (Color) key; String colourCode = ColorUtils.toTkCode(colour); if (!firstColour) { @@ -106,55 +117,68 @@ public class ChimeraCommands } sb.append("color ").append(colourCode).append(" "); firstColour = false; - boolean firstModelForColour = true; - final Map>> colourData = colourMap - .get(colour); - for (Integer model : colourData.keySet()) + final AtomSpecModel colourData = colourMap.get(colour); + sb.append(colourData.getAtomSpec()); + } + commands.add(sb.toString()); + return commands; + } + + /** + * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and + * builds a Chimera format atom spec + * + * @param modelAndChainRanges + */ + protected static String getAtomSpec( + Map>> modelAndChainRanges) + { + StringBuilder sb = new StringBuilder(128); + boolean firstModelForColour = true; + for (Integer model : modelAndChainRanges.keySet()) + { + boolean firstPositionForModel = true; + if (!firstModelForColour) { - boolean firstPositionForModel = true; - if (!firstModelForColour) - { - sb.append("|"); - } - firstModelForColour = false; - sb.append("#").append(model).append(":"); + sb.append("|"); + } + firstModelForColour = false; + sb.append("#").append(model).append(":"); - final Map> modelData = colourData.get(model); - for (String chain : modelData.keySet()) + final Map> modelData = modelAndChainRanges + .get(model); + for (String chain : modelData.keySet()) + { + boolean hasChain = !"".equals(chain.trim()); + for (int[] range : modelData.get(chain)) { - boolean hasChain = !"".equals(chain.trim()); - for (int[] range : modelData.get(chain)) + if (!firstPositionForModel) { - if (!firstPositionForModel) - { - sb.append(","); - } - if (range[0] == range[1]) - { - sb.append(range[0]); - } - else - { - sb.append(range[0]).append("-").append(range[1]); - } - if (hasChain) - { - sb.append(".").append(chain); - } - firstPositionForModel = false; + sb.append(","); } + if (range[0] == range[1]) + { + sb.append(range[0]); + } + else + { + sb.append(range[0]).append("-").append(range[1]); + } + if (hasChain) + { + sb.append(".").append(chain); + } + firstPositionForModel = false; } } } - commands.add(sb.toString()); - return commands; + return sb.toString(); } /** *
    -   * Build a data structure which maps contiguous subsequences for each colour. 
    -   * This generates a data structure from which we can easily generate the 
    -   * Chimera command for colour by sequence.
    +   * Build a data structure which records contiguous subsequences for each colour. 
    +   * From this we can easily generate the Chimera command for colour by sequence.
        * Color
        *     Model number
        *         Chain
    @@ -162,13 +186,19 @@ public class ChimeraCommands
        * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
        * 
    */ - protected static Map>>> buildColoursMap( + protected static Map buildColoursMap( StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + SequenceI[][] sequence, SequenceRenderer sr, + AlignmentViewPanel viewPanel) { - Map>>> colourMap = new LinkedHashMap>>>(); + FeatureRenderer fr = viewPanel.getFeatureRenderer(); + FeatureColourFinder finder = new FeatureColourFinder(fr); + AlignViewportI viewport = viewPanel.getAlignViewport(); + ColumnSelection cs = viewport.getColumnSelection(); + AlignmentI al = viewport.getAlignment(); + Map colourMap = new LinkedHashMap(); Color lastColour = null; + for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); @@ -186,9 +216,9 @@ public class ChimeraCommands { final SequenceI seq = sequence[pdbfnum][s]; if (mapping[m].getSequence() == seq - && (sp = alignment.findIndex(seq)) > -1) + && (sp = al.findIndex(seq)) > -1) { - SequenceI asp = alignment.getSequenceAt(sp); + SequenceI asp = al.getSequenceAt(sp); for (int r = 0; r < asp.getLength(); r++) { // no mapping to gaps in sequence @@ -203,7 +233,17 @@ public class ChimeraCommands continue; } - Color colour = sr.getResidueColour(seq, r, fr); + Color colour = sr.getResidueColour(seq, r, finder); + + /* + * darker colour for hidden regions + */ + if (!cs.isVisible(r)) + { + // colour = ColorUtils.darkerThan(colour); + colour = Color.GRAY; + } + final String chain = mapping[m].getChain(); /* @@ -230,8 +270,8 @@ public class ChimeraCommands // final colour range if (lastColour != null) { - addColourRange(colourMap, lastColour, pdbfnum, startPos, - lastPos, lastChain); + addColourRange(colourMap, lastColour, pdbfnum, startPos, lastPos, + lastChain); } // break; } @@ -244,51 +284,266 @@ public class ChimeraCommands /** * Helper method to add one contiguous colour range to the colour map. * - * @param colourMap - * @param colour + * @param map + * @param key * @param model * @param startPos * @param endPos * @param chain */ - protected static void addColourRange( - Map>>> colourMap, - Color colour, int model, int startPos, int endPos, String chain) + protected static void addColourRange(Map map, + Object key, int model, int startPos, int endPos, String chain) { /* * Get/initialize map of data for the colour */ - SortedMap>> colourData = colourMap - .get(colour); - if (colourData == null) + AtomSpecModel atomSpec = map.get(key); + if (atomSpec == null) { - colourMap - .put(colour, - colourData = new TreeMap>>()); + atomSpec = new AtomSpecModel(); + map.put(key, atomSpec); } - /* - * Get/initialize map of data for the colour and model - */ - Map> modelData = colourData.get(model); - if (modelData == null) + atomSpec.addRange(model, startPos, endPos, chain); + } + + /** + * Constructs and returns Chimera commands to set attributes on residues + * corresponding to features in Jalview. Attribute names are the Jalview + * feature type, with a "jv_" prefix. + * + * @param ssm + * @param files + * @param seqs + * @param viewPanel + * @return + */ + public static StructureMappingcommandSet getSetAttributeCommandsForFeatures( + StructureSelectionManager ssm, String[] files, + SequenceI[][] seqs, AlignmentViewPanel viewPanel) + { + Map> featureMap = buildFeaturesMap( + ssm, files, seqs, viewPanel); + + List commands = buildSetAttributeCommands(featureMap); + + StructureMappingcommandSet cs = new StructureMappingcommandSet( + ChimeraCommands.class, null, + commands.toArray(new String[commands.size()])); + + return cs; + } + + /** + *
    +   * Helper method to build a map of 
    +   *   { featureType, { feature value, AtomSpecModel } }
    +   * 
    + * + * @param ssm + * @param files + * @param seqs + * @param viewPanel + * @return + */ + protected static Map> buildFeaturesMap( + StructureSelectionManager ssm, String[] files, + SequenceI[][] seqs, AlignmentViewPanel viewPanel) + { + Map> theMap = new LinkedHashMap>(); + + FeatureRenderer fr = viewPanel.getFeatureRenderer(); + if (fr == null) { - colourData.put(model, modelData = new TreeMap>()); + return theMap; } - /* - * Get/initialize map of data for colour, model and chain - */ - List chainData = modelData.get(chain); - if (chainData == null) + List visibleFeatures = fr.getDisplayedFeatureTypes(); + if (visibleFeatures.isEmpty()) { - modelData.put(chain, chainData = new ArrayList()); + return theMap; } + AlignmentI alignment = viewPanel.getAlignment(); + for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) + { + StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); + + if (mapping == null || mapping.length < 1) + { + continue; + } + + for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++) + { + for (int m = 0; m < mapping.length; m++) + { + final SequenceI seq = seqs[pdbfnum][seqNo]; + int sp = alignment.findIndex(seq); + if (mapping[m].getSequence() == seq && sp > -1) + { + /* + * found a sequence with a mapping to a structure; + * now scan its features + */ + SequenceI asp = alignment.getSequenceAt(sp); + + scanSequenceFeatures(visibleFeatures, mapping[m], asp, theMap, + pdbfnum); + } + } + } + } + return theMap; + } + + /** + * Inspect features on the sequence; for each feature that is visible, + * determine its mapped ranges in the structure (if any) according to the + * given mapping, and add them to the map + * + * @param visibleFeatures + * @param mapping + * @param seq + * @param theMap + * @param modelNumber + */ + protected static void scanSequenceFeatures(List visibleFeatures, + StructureMapping mapping, SequenceI seq, + Map> theMap, int modelNumber) + { + SequenceFeature[] sfs = seq.getSequenceFeatures(); + if (sfs == null) + { + return; + } + + for (SequenceFeature sf : sfs) + { + String type = sf.getType(); + + /* + * Only copy visible features, don't copy any which originated + * from Chimera, and suppress uninteresting ones (e.g. RESNUM) + */ + boolean isFromViewer = JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup()); + if (isFromViewer || !visibleFeatures.contains(type)) + { + continue; + } + List mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(), + sf.getEnd()); + + if (!mappedRanges.isEmpty()) + { + String value = sf.getDescription(); + if (value == null || value.length() == 0) + { + value = type; + } + float score = sf.getScore(); + if (score != 0f && !Float.isNaN(score)) + { + value = Float.toString(score); + } + Map featureValues = theMap.get(type); + if (featureValues == null) + { + featureValues = new HashMap(); + theMap.put(type, featureValues); + } + for (int[] range : mappedRanges) + { + addColourRange(featureValues, value, modelNumber, range[0], range[1], + mapping.getChain()); + } + } + } + } + + /** + * Traverse the map of features/values/models/chains/positions to construct a + * list of 'setattr' commands (one per distinct feature type and value). + *

    + * The format of each command is + * + *

    +   * 
    setattr r " " #modelnumber:range.chain + * e.g. setattr r jv:chain #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... + *
    + *
    + * + * @param featureMap + * @return + */ + protected static List buildSetAttributeCommands( + Map> featureMap) + { + List commands = new ArrayList(); + for (String featureType : featureMap.keySet()) + { + String attributeName = makeAttributeName(featureType); + + /* + * clear down existing attributes for this feature + */ + // 'problem' - sets attribute to None on all residues - overkill? + // commands.add("~setattr r " + attributeName + " :*"); + + Map values = featureMap.get(featureType); + for (Object value : values.keySet()) + { + /* + * for each distinct value recorded for this feature type, + * add a command to set the attribute on the mapped residues + * Put values in single quotes, encoding any embedded single quotes + */ + StringBuilder sb = new StringBuilder(128); + String featureValue = value.toString(); + featureValue = featureValue.replaceAll("\\'", "'"); + sb.append("setattr r ").append(attributeName).append(" '") + .append(featureValue).append("' "); + sb.append(values.get(value).getAtomSpec()); + commands.add(sb.toString()); + } + } + + return commands; + } + + /** + * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied + * for a 'Jalview' namespace, and any non-alphanumeric character is converted + * to an underscore. + * + * @param featureType + * @return
    +   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
    +   * 
    + */ + protected static String makeAttributeName(String featureType) + { + StringBuilder sb = new StringBuilder(); + if (featureType != null) + { + for (char c : featureType.toCharArray()) + { + sb.append(Character.isLetterOrDigit(c) ? c : '_'); + } + } + String attName = NAMESPACE_PREFIX + sb.toString(); + /* - * Add the start/end positions + * Chimera treats an attribute name ending in 'color' as colour-valued; + * Jalview doesn't, so prevent this by appending an underscore */ - chainData.add(new int[] { startPos, endPos }); + if (attName.toUpperCase().endsWith("COLOR")) + { + attName += "_"; + } + + return attName; } } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraListener.java b/src/jalview/ext/rbvi/chimera/ChimeraListener.java index ba5e332..507ddbc 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraListener.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraListener.java @@ -129,7 +129,7 @@ public class ChimeraListener extends AbstractRequestHandler implements } /** - * Handler a ModelChanged notification from Chimera + * Handle a ModelChanged notification from Chimera * * @param substring */ diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index b05c168..fad3137 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -21,13 +21,15 @@ package jalview.ext.rbvi.chimera; import jalview.api.AlignmentViewPanel; -import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.PDBEntry; +import jalview.datamodel.SearchResultMatchI; +import jalview.datamodel.SearchResultsI; +import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.httpserver.AbstractRequestHandler; import jalview.io.DataSourceType; @@ -40,8 +42,14 @@ import jalview.structures.models.AAStructureBindingModel; import jalview.util.MessageManager; import java.awt.Color; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; import java.net.BindException; import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; @@ -54,6 +62,8 @@ import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType; public abstract class JalviewChimeraBinding extends AAStructureBindingModel { + public static final String CHIMERA_FEATURE_GROUP = "Chimera"; + // Chimera clause to exclude alternate locations in atom selection private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9"; @@ -98,14 +108,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel */ private Map> chimeraMaps = new LinkedHashMap>(); - /* - * the default or current model displayed if the model cannot be identified - * from the selection message - */ - private int frameNo = 0; - - private String lastCommand; - String lastHighlightCommand; /* @@ -171,11 +173,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel { getSsm().addStructureViewerListener(this); // ssm.addSelectionListener(this); - FeatureRenderer fr = getFeatureRenderer(null); - if (fr != null) - { - fr.featuresAdded(); - } refreshGUI(); } return true; @@ -304,7 +301,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel chimeraListener.shutdown(); chimeraListener = null; } - lastCommand = null; viewer = null; if (chimeraMonitor != null) @@ -339,21 +335,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * Construct and send a command to align structures against a reference - * structure, based on one or more sequence alignments - * - * @param _alignment - * an array of alignments to process - * @param _refStructure - * an array of corresponding reference structures (index into pdb - * file array); if a negative value is passed, the first PDB file - * mapped to an alignment sequence is used as the reference for - * superposition - * @param _hiddenCols - * an array of corresponding hidden columns for each alignment + * {@inheritDoc} */ @Override - public void superposeStructures(AlignmentI[] _alignment, + public String superposeStructures(AlignmentI[] _alignment, int[] _refStructure, ColumnSelection[] _hiddenCols) { StringBuilder allComs = new StringBuilder(128); @@ -361,7 +346,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel if (!waitForFileLoad(files)) { - return; + return null; } refreshPdbEntries(); @@ -380,13 +365,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /* - * 'matched' array will hold 'true' for visible alignment columns where + * 'matched' bit i will be set for visible alignment columns i where * all sequences have a residue with a mapping to the PDB structure */ - boolean matched[] = new boolean[alignment.getWidth()]; - for (int m = 0; m < matched.length; m++) + BitSet matched = new BitSet(); + for (int m = 0; m < alignment.getWidth(); m++) { - matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true; + if (hiddenCols == null || hiddenCols.isVisible(m)) + { + matched.set(m); + } } SuperposeData[] structures = new SuperposeData[files.length]; @@ -410,17 +398,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel refStructure = candidateRefStructure; } - int nmatched = 0; - for (boolean b : matched) - { - if (b) - { - nmatched++; - } - } + int nmatched = matched.cardinality(); if (nmatched < 4) { - // TODO: bail out here because superposition illdefined? + return MessageManager.formatMessage("label.insufficient_residues", + nmatched); } /* @@ -433,41 +415,41 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel int lpos = -1; boolean run = false; StringBuilder molsel = new StringBuilder(); - for (int r = 0; r < matched.length; r++) + + int nextColumnMatch = matched.nextSetBit(0); + while (nextColumnMatch != -1) { - if (matched[r]) + int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch]; + if (lpos != pdbResNum - 1) { - int pdbResNum = structures[pdbfnum].pdbResNo[r]; - if (lpos != pdbResNum - 1) + /* + * discontiguous - append last residue now + */ + if (lpos != -1) { - /* - * discontiguous - append last residue now - */ - if (lpos != -1) - { - molsel.append(String.valueOf(lpos)); - molsel.append(chainCd); - molsel.append(","); - } - run = false; + molsel.append(String.valueOf(lpos)); + molsel.append(chainCd); + molsel.append(","); } - else + run = false; + } + else + { + /* + * extending a contiguous run + */ + if (!run) { /* - * extending a contiguous run + * start the range selection */ - if (!run) - { - /* - * start the range selection - */ - molsel.append(String.valueOf(lpos)); - molsel.append("-"); - } - run = true; + molsel.append(String.valueOf(lpos)); + molsel.append("-"); } - lpos = pdbResNum; + run = true; } + lpos = pdbResNum; + nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1); } /* @@ -543,6 +525,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel .append(";" + command.toString()); } } + + String error = null; if (selectioncom.length() > 0) { // TODO: visually distinguish regions that were superposed @@ -556,9 +540,17 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } allComs.append("; ~display all; chain @CA|P; ribbon ") .append(selectioncom.toString()).append("; focus"); - sendChimeraCommand(allComs.toString(), false); + List chimeraReplies = sendChimeraCommand(allComs.toString(), + true); + for (String reply : chimeraReplies) + { + if (reply.toLowerCase().contains("unequal numbers of atoms")) + { + error = reply; + } + } } - + return error; } /** @@ -631,31 +623,42 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * Send a command to Chimera, and optionally log any responses. + * Send a command to Chimera, and optionally log and return any responses. + *

    + * Does nothing, and returns null, if the command is the same as the last one + * sent [why?]. * * @param command - * @param logResponse + * @param getResponse */ - public void sendChimeraCommand(final String command, boolean logResponse) + public List sendChimeraCommand(final String command, + boolean getResponse) { if (viewer == null) { // ? thread running after viewer shut down - return; + return null; } + List reply = null; viewerCommandHistory(false); - if (lastCommand == null || !lastCommand.equals(command)) + if (true /*lastCommand == null || !lastCommand.equals(command)*/) { // trim command or it may never find a match in the replyLog!! List lastReply = viewer.sendChimeraCommand(command.trim(), - logResponse); - if (logResponse && debug) + getResponse); + if (getResponse) { - log("Response from command ('" + command + "') was:\n" + lastReply); + reply = lastReply; + if (debug) + { + log("Response from command ('" + command + "') was:\n" + + lastReply); + } } } viewerCommandHistory(true); - lastCommand = command; + + return reply; } /** @@ -689,17 +692,15 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel /** * @param files * @param sr - * @param fr - * @param alignment + * @param viewPanel * @return */ @Override protected StructureMappingcommandSet[] getColourBySequenceCommands( - String[] files, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel) { return ChimeraCommands.getColourBySequenceCommand(getSsm(), files, - getSequence(), sr, fr, alignment); + getSequence(), sr, viewPanel); } /** @@ -742,6 +743,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel */ private int _modelFileNameMap[]; + // //////////////////////////////// // /StructureListener @Override @@ -837,59 +839,64 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * Parse model number, residue and chain for each selected position, * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain) */ + List atomSpecs = convertStructureResiduesToAlignment(selection); + + /* + * Broadcast the selection (which may be empty, if the user just cleared all + * selections) + */ + getSsm().mouseOverStructure(atomSpecs); + } + + /** + * Converts a list of Chimera atomspecs to a list of AtomSpec representing the + * corresponding residues (if any) in Jalview + * + * @param structureSelection + * @return + */ + protected List convertStructureResiduesToAlignment( + List structureSelection) + { List atomSpecs = new ArrayList(); - for (String atomSpec : selection) + for (String atomSpec : structureSelection) { - int colonPos = atomSpec.indexOf(":"); - if (colonPos == -1) - { - continue; // malformed - } - - int hashPos = atomSpec.indexOf("#"); - String modelSubmodel = atomSpec.substring(hashPos + 1, colonPos); - int dotPos = modelSubmodel.indexOf("."); - int modelId = 0; try { - modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel - : modelSubmodel.substring(0, dotPos)); - } catch (NumberFormatException e) + AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec); + String pdbfilename = getPdbFileForModel(spec.getModelNumber()); + spec.setPdbFile(pdbfilename); + atomSpecs.add(spec); + } catch (IllegalArgumentException e) { - // ignore, default to model 0 + System.err.println("Failed to parse atomspec: " + atomSpec); } + } + return atomSpecs; + } - String residueChain = atomSpec.substring(colonPos + 1); - dotPos = residueChain.indexOf("."); - int pdbResNum = Integer.parseInt(dotPos == -1 ? residueChain - : residueChain.substring(0, dotPos)); - - String chainId = dotPos == -1 ? "" : residueChain - .substring(dotPos + 1); - - /* - * Work out the pdbfilename from the model number - */ - String pdbfilename = modelFileNames[frameNo]; - findfileloop: for (String pdbfile : this.chimeraMaps.keySet()) + /** + * @param modelId + * @return + */ + protected String getPdbFileForModel(int modelId) + { + /* + * Work out the pdbfilename from the model number + */ + String pdbfilename = modelFileNames[0]; + findfileloop: for (String pdbfile : this.chimeraMaps.keySet()) + { + for (ChimeraModel cm : chimeraMaps.get(pdbfile)) { - for (ChimeraModel cm : chimeraMaps.get(pdbfile)) + if (cm.getModelNumber() == modelId) { - if (cm.getModelNumber() == modelId) - { - pdbfilename = pdbfile; - break findfileloop; - } + pdbfilename = pdbfile; + break findfileloop; } } - atomSpecs.add(new AtomSpec(pdbfilename, chainId, pdbResNum, 0)); } - - /* - * Broadcast the selection (which may be empty, if the user just cleared all - * selections) - */ - getSsm().mouseOverStructure(atomSpecs); + return pdbfilename; } private void log(String message) @@ -1036,6 +1043,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** + * Returns a list of chains mapped in this viewer. Note this list is not + * currently scoped per structure. + * + * @return + */ + @Override + public List getChainNames() + { + return chainNames; + } + + /** * Send a 'focus' command to Chimera to recentre the visible display */ public void focusView() @@ -1071,13 +1090,207 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } } + /** + * Constructs and send commands to Chimera to set attributes on residues for + * features visible in Jalview + * + * @param avp + * @return + */ + public int sendFeaturesToViewer(AlignmentViewPanel avp) + { + // TODO refactor as required to pull up to an interface + AlignmentI alignment = avp.getAlignment(); - @Override - public List getChainNames() + String[] files = getPdbFile(); + if (files == null) + { + return 0; + } + + StructureMappingcommandSet commandSet = ChimeraCommands + .getSetAttributeCommandsForFeatures(getSsm(), files, + getSequence(), avp); + String[] commands = commandSet.commands; + if (commands.length > 10) + { + sendCommandsByFile(commands); + } + else + { + for (String command : commands) + { + sendAsynchronousCommand(command, null); + } + } + return commands.length; + } + + /** + * Write commands to a temporary file, and send a command to Chimera to open + * the file as a commands script. For use when sending a large number of + * separate commands would overload the REST interface mechanism. + * + * @param commands + */ + protected void sendCommandsByFile(String[] commands) { - return chainNames; + try + { + File tmp = File.createTempFile("chim", ".com"); + tmp.deleteOnExit(); + PrintWriter out = new PrintWriter(new FileOutputStream(tmp)); + for (String command : commands) + { + out.println(command); + } + out.flush(); + out.close(); + String path = tmp.getAbsolutePath(); + sendAsynchronousCommand("open cmd:" + path, null); + } catch (IOException e) + { + System.err + .println("Sending commands to Chimera via file failed with " + + e.getMessage()); + } } + /** + * Get Chimera residues which have the named attribute, find the mapped + * positions in the Jalview sequence(s), and set as sequence features + * + * @param attName + * @param alignmentPanel + */ + public void copyStructureAttributesToFeatures(String attName, + AlignmentViewPanel alignmentPanel) + { + // todo pull up to AAStructureBindingModel (and interface?) + + /* + * ask Chimera to list residues with the attribute, reporting its value + */ + // this alternative command + // list residues spec ':*/attName' attr attName + // doesn't report 'None' values (which is good), but + // fails for 'average.bfactor' (which is bad): + + String cmd = "list residues attr '" + attName + "'"; + List residues = sendChimeraCommand(cmd, true); + + boolean featureAdded = createFeaturesForAttributes(attName, residues); + if (featureAdded) + { + alignmentPanel.getFeatureRenderer().featuresAdded(); + } + } + + /** + * Create features in Jalview for the given attribute name and structure + * residues. + * + *

    +   * The residue list should be 0, 1 or more reply lines of the format: 
    +   *     residue id #0:5.A isHelix -155.000836316 index 5 
    +   * or 
    +   *     residue id #0:6.A isHelix None
    +   * 
    + * + * @param attName + * @param residues + * @return + */ + protected boolean createFeaturesForAttributes(String attName, + List residues) + { + boolean featureAdded = false; + String featureGroup = getViewerFeatureGroup(); + + for (String residue : residues) + { + AtomSpec spec = null; + String[] tokens = residue.split(" "); + if (tokens.length < 5) + { + continue; + } + String atomSpec = tokens[2]; + String attValue = tokens[4]; + + /* + * ignore 'None' (e.g. for phi) or 'False' (e.g. for isHelix) + */ + if ("None".equalsIgnoreCase(attValue) + || "False".equalsIgnoreCase(attValue)) + { + continue; + } + + try + { + spec = AtomSpec.fromChimeraAtomspec(atomSpec); + } catch (IllegalArgumentException e) + { + System.err.println("Problem parsing atomspec " + atomSpec); + continue; + } + + String chainId = spec.getChain(); + String description = attValue; + float score = Float.NaN; + try + { + score = Float.valueOf(attValue); + description = chainId; + } catch (NumberFormatException e) + { + // was not a float value + } + + String pdbFile = getPdbFileForModel(spec.getModelNumber()); + spec.setPdbFile(pdbFile); + + List atoms = Collections.singletonList(spec); + + /* + * locate the mapped position in the alignment (if any) + */ + SearchResultsI sr = getSsm() + .findAlignmentPositionsForStructurePositions(atoms); + + /* + * expect one matched alignment position, or none + * (if the structure position is not mapped) + */ + for (SearchResultMatchI m : sr.getResults()) + { + SequenceI seq = m.getSequence(); + int start = m.getStart(); + int end = m.getEnd(); + SequenceFeature sf = new SequenceFeature(attName, description, + start, end, score, featureGroup); + // todo: should SequenceFeature have an explicit property for chain? + // note: repeating the action shouldn't duplicate features + featureAdded |= seq.addSequenceFeature(sf); + } + } + return featureAdded; + } + + /** + * Answers the feature group name to apply to features created in Jalview from + * Chimera attributes + * + * @return + */ + protected String getViewerFeatureGroup() + { + // todo pull up to interface + return CHIMERA_FEATURE_GROUP; + } + + public Hashtable getChainFile() { return chainFile; diff --git a/src/jalview/ext/varna/VarnaCommands.java b/src/jalview/ext/varna/VarnaCommands.java index a41e10e..d65f1d5 100644 --- a/src/jalview/ext/varna/VarnaCommands.java +++ b/src/jalview/ext/varna/VarnaCommands.java @@ -20,10 +20,10 @@ */ package jalview.ext.varna; -import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.StructureMapping; import jalview.structure.StructureSelectionManager; @@ -47,7 +47,8 @@ public class VarnaCommands */ public static String[] getColourBySequenceCommand( StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, + SequenceI[][] sequence, SequenceRenderer sr, + FeatureColourFinder finder, AlignmentI alignment) { ArrayList str = new ArrayList(); @@ -58,7 +59,9 @@ public class VarnaCommands StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); if (mapping == null || mapping.length < 1) + { continue; + } int lastPos = -1; for (int s = 0; s < sequence[pdbfnum].length; s++) @@ -79,14 +82,15 @@ public class VarnaCommands int pos = mapping[m].getPDBResNum(asp.findPosition(r)); if (pos < 1 || pos == lastPos) + { continue; + } lastPos = pos; - Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r); + Color col = sr.getResidueColour(sequence[pdbfnum][s], r, + finder); - if (fr != null) - col = fr.findFeatureColour(col, sequence[pdbfnum][s], r); String newSelcom = (mapping[m].getChain() != " " ? ":" + mapping[m].getChain() : "") + "/" diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 468fcd3..445e967 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -86,6 +86,7 @@ import jalview.schemes.ResidueProperties; import jalview.schemes.TCoffeeColourScheme; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import jalview.ws.DBRefFetcher; import jalview.ws.DBRefFetcher.FetchFinishedListenerI; import jalview.ws.jws1.Discoverer; @@ -160,6 +161,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, AlignViewport viewport; + ViewportRanges vpRanges; + public AlignViewControllerI avc; List alignPanels = new ArrayList(); @@ -331,6 +334,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, progressBar = new ProgressBar(this.statusPanel, this.statusBar); } + vpRanges = viewport.getRanges(); avc = new jalview.controller.AlignViewController(this, viewport, alignPanel); if (viewport.getAlignmentConservationAnnotation() == null) @@ -640,8 +644,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, new String[] { (viewport.cursorMode ? "on" : "off") })); if (viewport.cursorMode) { - alignPanel.getSeqPanel().seqCanvas.cursorX = viewport.startRes; - alignPanel.getSeqPanel().seqCanvas.cursorY = viewport.startSeq; + alignPanel.getSeqPanel().seqCanvas.cursorX = vpRanges + .getStartRes(); + alignPanel.getSeqPanel().seqCanvas.cursorY = vpRanges + .getStartSeq(); } alignPanel.getSeqPanel().seqCanvas.repaint(); break; @@ -679,8 +685,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - alignPanel.setScrollValues(viewport.startRes, viewport.startSeq - - viewport.endSeq + viewport.startSeq); + alignPanel.setScrollValues(vpRanges.getStartRes(), + 2 * vpRanges.getStartSeq() - vpRanges.getEndSeq()); } break; case KeyEvent.VK_PAGE_DOWN: @@ -690,8 +696,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - alignPanel.setScrollValues(viewport.startRes, viewport.startSeq - + viewport.endSeq - viewport.startSeq); + alignPanel.setScrollValues(vpRanges.getStartRes(), + vpRanges.getEndSeq()); } break; } @@ -2141,7 +2147,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { // propagate alignment changed. - viewport.setEndSeq(alignment.getHeight()); + vpRanges.setEndSeq(alignment.getHeight()); if (annotationAdded) { // Duplicate sequence annotation in all views. @@ -2545,7 +2551,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { trimRegion = new TrimRegionCommand("Remove Left", true, seqs, column, viewport.getAlignment()); - viewport.setStartRes(0); + vpRanges.setStartRes(0); } else { @@ -2612,13 +2618,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, // This is to maintain viewport position on first residue // of first sequence SequenceI seq = viewport.getAlignment().getSequenceAt(0); - int startRes = seq.findPosition(viewport.startRes); + int startRes = seq.findPosition(vpRanges.getStartRes()); // ShiftList shifts; // viewport.getAlignment().removeGaps(shifts=new ShiftList()); // edit.alColumnChanges=shifts.getInverse(); // if (viewport.hasHiddenColumns) // viewport.getColumnSelection().compensateForEdits(shifts); - viewport.setStartRes(seq.findIndex(startRes) - 1); + vpRanges.setStartRes(seq.findIndex(startRes) - 1); viewport.firePropertyChange("alignment", null, viewport.getAlignment() .getSequences()); @@ -2651,12 +2657,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, // This is to maintain viewport position on first residue // of first sequence SequenceI seq = viewport.getAlignment().getSequenceAt(0); - int startRes = seq.findPosition(viewport.startRes); + int startRes = seq.findPosition(vpRanges.getStartRes()); addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end, viewport.getAlignment())); - viewport.setStartRes(seq.findIndex(startRes) - 1); + vpRanges.setStartRes(seq.findIndex(startRes) - 1); viewport.firePropertyChange("alignment", null, viewport.getAlignment() .getSequences()); diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 499faaf..602e3a1 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -46,12 +46,12 @@ import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemeProperty; import jalview.schemes.ResidueColourScheme; import jalview.schemes.UserColourScheme; -import jalview.structure.CommandListener; import jalview.structure.SelectionSource; import jalview.structure.StructureSelectionManager; import jalview.structure.VamsasSource; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import jalview.ws.params.AutoCalcSetting; import java.awt.Container; @@ -72,7 +72,7 @@ import javax.swing.JInternalFrame; * @version $Revision: 1.141 $ */ public class AlignViewport extends AlignmentViewport implements - SelectionSource, CommandListener + SelectionSource { Font font; @@ -238,10 +238,7 @@ public class AlignViewport extends AlignmentViewport implements void init() { - this.startRes = 0; - this.endRes = alignment.getWidth() - 1; - this.startSeq = 0; - this.endSeq = alignment.getHeight() - 1; + ranges = new ViewportRanges(this.alignment); applyViewProperties(); String fontName = Cache.getDefault("FONT_NAME", "SansSerif"); @@ -384,11 +381,6 @@ public class AlignViewport extends AlignmentViewport implements super.setViewStyle(settingsForView); setFont(new Font(viewStyle.getFontName(), viewStyle.getFontStyle(), viewStyle.getFontSize()), false); - if (residueShading != null) - { - residueShading.setConservationApplied(settingsForView - .isConservationColourSelected()); - } } /** @@ -860,7 +852,7 @@ public class AlignViewport extends AlignmentViewport implements } } - setEndSeq(getAlignment().getHeight()); + ranges.setEndSeq(getAlignment().getHeight()); firePropertyChange("alignment", null, getAlignment().getSequences()); } diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index e61b042..ac137b9 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -35,6 +35,7 @@ import jalview.schemes.ResidueProperties; import jalview.structure.StructureSelectionManager; import jalview.util.MessageManager; import jalview.util.Platform; +import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Color; @@ -69,6 +70,8 @@ public class AlignmentPanel extends GAlignmentPanel implements { public AlignViewport av; + ViewportRanges vpRanges; + OverviewPanel overviewPanel; private SeqPanel seqPanel; @@ -91,9 +94,9 @@ public class AlignmentPanel extends GAlignmentPanel implements // this value is set false when selection area being dragged boolean fastPaint = true; - int hextent = 0; + private int hextent = 0; - int vextent = 0; + private int vextent = 0; /* * Flag set while scrolling to follow complementary cDNA/protein scroll. When @@ -113,6 +116,7 @@ public class AlignmentPanel extends GAlignmentPanel implements { alignFrame = af; this.av = av; + vpRanges = av.getRanges(); setSeqPanel(new SeqPanel(av, this)); setIdPanel(new IdPanel(av, this)); @@ -377,7 +381,7 @@ public class AlignmentPanel extends GAlignmentPanel implements */ if (centre) { - int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1; + int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1; start = Math.max(start - offset, 0); end = end + offset - 1; } @@ -413,7 +417,7 @@ public class AlignmentPanel extends GAlignmentPanel implements // + av.getStartSeq() + ", ends=" + av.getEndSeq()); if (!av.getWrapAlignment()) { - if ((startv = av.getStartRes()) >= start) + if ((startv = vpRanges.getStartRes()) >= start) { /* * Scroll left to make start of search results visible @@ -421,7 +425,7 @@ public class AlignmentPanel extends GAlignmentPanel implements // setScrollValues(start - 1, seqIndex); // plus one residue setScrollValues(start, seqIndex); } - else if ((endv = av.getEndRes()) <= end) + else if ((endv = vpRanges.getEndRes()) <= end) { /* * Scroll right to make end of search results visible @@ -429,19 +433,20 @@ public class AlignmentPanel extends GAlignmentPanel implements // setScrollValues(startv + 1 + end - endv, seqIndex); // plus one setScrollValues(startv + end - endv, seqIndex); } - else if ((starts = av.getStartSeq()) > seqIndex) + else if ((starts = vpRanges.getStartSeq()) > seqIndex) { /* * Scroll up to make start of search results visible */ - setScrollValues(av.getStartRes(), seqIndex); + setScrollValues(vpRanges.getStartRes(), seqIndex); } - else if ((ends = av.getEndSeq()) <= seqIndex) + else if ((ends = vpRanges.getEndSeq()) <= seqIndex) { /* * Scroll down to make end of search results visible */ - setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1); + setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends + + 1); } /* * Else results are already visible - no need to scroll @@ -464,10 +469,11 @@ public class AlignmentPanel extends GAlignmentPanel implements { int cwidth = getSeqPanel().seqCanvas .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth()); - if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth)) + if (res < vpRanges.getStartRes() + || res >= (vpRanges.getStartRes() + cwidth)) { vscroll.setValue((res / cwidth)); - av.startRes = vscroll.getValue() * cwidth; + vpRanges.setStartRes(vscroll.getValue() * cwidth); } } @@ -591,7 +597,7 @@ public class AlignmentPanel extends GAlignmentPanel implements fontChanged(); setAnnotationVisible(av.isShowAnnotation()); boolean wrap = av.getWrapAlignment(); - av.startSeq = 0; + vpRanges.setStartSeq(0); scalePanelHolder.setVisible(!wrap); hscroll.setVisible(!wrap); idwidthAdjuster.setVisible(!wrap); @@ -688,7 +694,6 @@ public class AlignmentPanel extends GAlignmentPanel implements */ public void setScrollValues(int x, int y) { - // System.err.println("Scroll " + this.av.viewName + " to " + x + "," + y); if (av == null || av.getAlignment() == null) { return; @@ -698,12 +703,10 @@ public class AlignmentPanel extends GAlignmentPanel implements if (av.hasHiddenColumns()) { + // reset the width to exclude hidden columns width = av.getColumnSelection().findColumnPosition(width); } - av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av - .getCharWidth())) - 1); - hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth(); vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight(); @@ -737,6 +740,10 @@ public class AlignmentPanel extends GAlignmentPanel implements x = 0; } + // update endRes after x has (possibly) been adjusted + vpRanges.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av + .getCharWidth())) - 1); + /* * each scroll adjustment triggers adjustmentValueChanged, which resets the * 'do not scroll complement' flag; ensure it is the same for both @@ -757,14 +764,14 @@ public class AlignmentPanel extends GAlignmentPanel implements @Override public void adjustmentValueChanged(AdjustmentEvent evt) { - int oldX = av.getStartRes(); - int oldY = av.getStartSeq(); + int oldX = vpRanges.getStartRes(); + int oldY = vpRanges.getStartSeq(); if (evt.getSource() == hscroll) { int x = hscroll.getValue(); - av.setStartRes(x); - av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av + vpRanges.setStartRes(x); + vpRanges.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av .getCharWidth())) - 1); } @@ -778,8 +785,8 @@ public class AlignmentPanel extends GAlignmentPanel implements { int rowSize = getSeqPanel().seqCanvas .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth()); - av.setStartRes(offy * rowSize); - av.setEndRes((offy + 1) * rowSize); + vpRanges.setStartRes(offy * rowSize); + vpRanges.setEndRes((offy + 1) * rowSize); } else { @@ -791,16 +798,18 @@ public class AlignmentPanel extends GAlignmentPanel implements @Override public void run() { - setScrollValues(av.getStartRes(), av.getStartSeq()); + setScrollValues(vpRanges.getStartRes(), + vpRanges.getStartSeq()); } }); } } else { - av.setStartSeq(offy); - av.setEndSeq(offy - + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight())); + vpRanges.setStartSeq(offy); + vpRanges.setEndSeq(offy + + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight()) + - 1); } } @@ -809,8 +818,8 @@ public class AlignmentPanel extends GAlignmentPanel implements overviewPanel.setBoxPosition(); } - int scrollX = av.startRes - oldX; - int scrollY = av.startSeq - oldY; + int scrollX = vpRanges.getStartRes() - oldX; + int scrollY = vpRanges.getStartSeq() - oldY; if (av.getWrapAlignment() || !fastPaint) { @@ -820,13 +829,13 @@ public class AlignmentPanel extends GAlignmentPanel implements { // Make sure we're not trying to draw a panel // larger than the visible window - if (scrollX > av.endRes - av.startRes) + if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes()) { - scrollX = av.endRes - av.startRes; + scrollX = vpRanges.getEndRes() - vpRanges.getStartRes(); } - else if (scrollX < av.startRes - av.endRes) + else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes()) { - scrollX = av.startRes - av.endRes; + scrollX = vpRanges.getStartRes() - vpRanges.getEndRes(); } if (scrollX != 0 || scrollY != 0) @@ -926,7 +935,7 @@ public class AlignmentPanel extends GAlignmentPanel implements } else { - setScrollValues(av.getStartRes(), av.getStartSeq()); + setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq()); } } diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index b1f0edb..84f3e6c 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -55,7 +55,6 @@ import java.util.List; import javax.swing.JColorChooser; import javax.swing.JMenuItem; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.Scrollable; @@ -708,7 +707,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, return; } - int column = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int column = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); if (av.hasHiddenColumns()) { @@ -905,7 +905,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, return; } } - imgWidth = (av.endRes - av.startRes + 1) * av.getCharWidth(); + imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes() + 1) + * av.getCharWidth(); if (imgWidth < 1) { return; @@ -946,7 +947,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, imageFresh = true; } - drawComponent(gg, av.startRes, av.endRes + 1); + drawComponent(gg, av.getRanges().getStartRes(), av.getRanges() + .getEndRes() + 1); imageFresh = false; g.drawImage(image, 0, 0, this); } @@ -976,8 +978,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.getCharWidth(), 0); long mtime = System.currentTimeMillis(); - int sr = av.startRes; - int er = av.endRes + 1; + int sr = av.getRanges().getStartRes(); + int er = av.getRanges().getEndRes() + 1; int transX = 0; if (horizontal > 0) // scrollbar pulled right, image to the left diff --git a/src/jalview/gui/AnnotationRowFilter.java b/src/jalview/gui/AnnotationRowFilter.java index 166e1ad..7705bc3 100644 --- a/src/jalview/gui/AnnotationRowFilter.java +++ b/src/jalview/gui/AnnotationRowFilter.java @@ -26,6 +26,8 @@ import jalview.datamodel.SequenceGroup; import jalview.schemes.AnnotationColourGradient; import jalview.util.MessageManager; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Vector; @@ -134,6 +136,14 @@ public abstract class AnnotationRowFilter extends JPanel { this.av = av; this.ap = ap; + thresholdValue.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + thresholdValue_actionPerformed(); + } + }); } public AnnotationRowFilter() diff --git a/src/jalview/gui/AppJmol.java b/src/jalview/gui/AppJmol.java index ffb9639..5d23f49 100644 --- a/src/jalview/gui/AppJmol.java +++ b/src/jalview/gui/AppJmol.java @@ -45,7 +45,6 @@ import java.util.Vector; import javax.swing.JCheckBoxMenuItem; import javax.swing.JInternalFrame; -import javax.swing.JMenu; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; @@ -147,7 +146,7 @@ public class AppJmol extends StructureViewerBase { super.initMenus(); - viewerActionMenu = new JMenu(MessageManager.getString("label.jmol")); + viewerActionMenu.setText(MessageManager.getString("label.jmol")); viewerColour .setText(MessageManager.getString("label.colour_with_jmol")); diff --git a/src/jalview/gui/AppJmolBinding.java b/src/jalview/gui/AppJmolBinding.java index 75e0c5e..f822358 100644 --- a/src/jalview/gui/AppJmolBinding.java +++ b/src/jalview/gui/AppJmolBinding.java @@ -40,8 +40,6 @@ public class AppJmolBinding extends JalviewJmolBinding { private AppJmol appJmolWindow; - private FeatureRenderer fr = null; - public AppJmolBinding(AppJmol appJmol, StructureSelectionManager sSm, PDBEntry[] pdbentry, SequenceI[][] sequenceIs, DataSourceType protocol) { @@ -50,26 +48,6 @@ public class AppJmolBinding extends JalviewJmolBinding } @Override - public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment) - { - AlignmentPanel ap = (alignment == null) ? appJmolWindow - .getAlignmentPanel() : (AlignmentPanel) alignment; - if (ap.av.isShowSequenceFeatures()) - { - if (fr == null) - { - fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer(); - } - else - { - ap.updateFeatureRenderer(fr); - } - } - - return fr; - } - - @Override public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment) { return new SequenceRenderer(((AlignmentPanel) alignment).av); @@ -215,4 +193,18 @@ public class AppJmolBinding extends JalviewJmolBinding { return appJmolWindow; } + + @Override + public jalview.api.FeatureRenderer getFeatureRenderer( + AlignmentViewPanel alignment) + { + AlignmentPanel ap = (alignment == null) ? appJmolWindow + .getAlignmentPanel() : (AlignmentPanel) alignment; + if (ap.av.isShowSequenceFeatures()) + { + return ap.av.getAlignPanel().getSeqPanel().seqCanvas.fr; + } + + return null; + } } diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java index 530f4fe..ec9feb7 100644 --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@ -20,10 +20,12 @@ */ package jalview.gui; +import jalview.api.FeatureRenderer; import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; +import jalview.ext.rbvi.chimera.ChimeraCommands; import jalview.ext.rbvi.chimera.JalviewChimeraBinding; import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; @@ -35,16 +37,22 @@ import jalview.util.Platform; import jalview.ws.dbsources.Pdb; import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Random; import javax.swing.JCheckBoxMenuItem; import javax.swing.JInternalFrame; +import javax.swing.JMenu; +import javax.swing.JMenuItem; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; @@ -58,8 +66,6 @@ public class ChimeraViewFrame extends StructureViewerBase { private JalviewChimeraBinding jmb; - private boolean allChainsSelected = false; - private IProgressIndicator progressBar = null; /* @@ -71,6 +77,10 @@ public class ChimeraViewFrame extends StructureViewerBase private Random random = new Random(); + private int myWidth = 500; + + private int myHeight = 150; + /** * Initialise menu options. */ @@ -89,6 +99,100 @@ public class ChimeraViewFrame extends StructureViewerBase helpItem.setText(MessageManager.getString("label.chimera_help")); savemenu.setVisible(false); // not yet implemented viewMenu.add(fitToWindow); + + JMenuItem writeFeatures = new JMenuItem( + MessageManager.getString("label.create_chimera_attributes")); + writeFeatures.setToolTipText(MessageManager + .getString("label.create_chimera_attributes_tip")); + writeFeatures.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + sendFeaturesToChimera(); + } + }); + viewerActionMenu.add(writeFeatures); + + final JMenu fetchAttributes = new JMenu( + MessageManager.getString("label.fetch_chimera_attributes")); + fetchAttributes.setToolTipText(MessageManager + .getString("label.fetch_chimera_attributes_tip")); + fetchAttributes.addMouseListener(new MouseAdapter() + { + + @Override + public void mouseEntered(MouseEvent e) + { + buildAttributesMenu(fetchAttributes); + } + }); + viewerActionMenu.add(fetchAttributes); + + } + + /** + * Query Chimera for its residue attribute names and add them as items off the + * attributes menu + * + * @param attributesMenu + */ + protected void buildAttributesMenu(JMenu attributesMenu) + { + List atts = jmb.sendChimeraCommand("list resattr", true); + if (atts == null) + { + return; + } + attributesMenu.removeAll(); + Collections.sort(atts); + for (String att : atts) + { + final String attName = att.split(" ")[1]; + + /* + * ignore 'jv_*' attributes, as these are Jalview features that have + * been transferred to residue attributes in Chimera! + */ + if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX)) + { + JMenuItem menuItem = new JMenuItem(attName); + menuItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + getChimeraAttributes(attName); + } + }); + attributesMenu.add(menuItem); + } + } + } + + /** + * Read residues in Chimera with the given attribute name, and set as features + * on the corresponding sequence positions (if any) + * + * @param attName + */ + protected void getChimeraAttributes(String attName) + { + jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel()); + } + + /** + * Send a command to Chimera to create residue attributes for Jalview features + *

    + * The syntax is: setattr r + *

    + * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A + */ + protected void sendFeaturesToChimera() + { + int count = jmb.sendFeaturesToViewer(getAlignmentPanel()); + statusBar.setText(MessageManager.formatMessage("label.attributes_set", + count)); } /** @@ -146,7 +250,6 @@ public class ChimeraViewFrame extends StructureViewerBase SequenceI[][] seqs) { createProgressBar(); - // FIXME extractChains needs pdbentries to match IDs to PDBEntry(s) on seqs jmb = new JalviewChimeraBindingModel(this, ap.getStructureSelectionManager(), pdbentrys, seqs, null); addAlignmentPanel(ap); @@ -158,7 +261,7 @@ public class ChimeraViewFrame extends StructureViewerBase useAlignmentPanelForSuperposition(ap); } jmb.setColourBySequence(true); - setSize(400, 400); // probably should be a configurable/dynamic default here + setSize(myWidth, myHeight); initMenus(); addingStructures = false; @@ -270,7 +373,7 @@ public class ChimeraViewFrame extends StructureViewerBase void initChimera() { jmb.setFinishedInit(false); - jalview.gui.Desktop.addInternalFrame(this, + Desktop.addInternalFrame(this, jmb.getViewerTitle(getViewerName(), true), getBounds().width, getBounds().height); @@ -294,12 +397,10 @@ public class ChimeraViewFrame extends StructureViewerBase + chimeraSessionFile); } } - jmb.setFinishedInit(true); jmb.startChimeraListener(); } - /** * Show only the selected chain(s) in the viewer */ @@ -450,6 +551,7 @@ public class ChimeraViewFrame extends StructureViewerBase if (files.length() > 0) { + jmb.setFinishedInit(false); if (!addingStructures) { try @@ -507,10 +609,21 @@ public class ChimeraViewFrame extends StructureViewerBase } } } + jmb.refreshGUI(); jmb.setFinishedInit(true); jmb.setLoadingFromArchive(false); + /* + * ensure that any newly discovered features (e.g. RESNUM) + * are added to any open feature settings dialog + */ + FeatureRenderer fr = getBinding().getFeatureRenderer(null); + if (fr != null) + { + fr.featuresAdded(); + } + // refresh the sequence colours for the new structure(s) for (AlignmentPanel ap : _colourwith) { @@ -656,7 +769,7 @@ public class ChimeraViewFrame extends StructureViewerBase { BrowserLauncher .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide"); - } catch (Exception ex) + } catch (IOException ex) { } } @@ -754,4 +867,20 @@ public class ChimeraViewFrame extends StructureViewerBase { return "Chimera"; } + + /** + * Sends commands to align structures according to associated alignment(s). + * + * @return + */ + @Override + protected String alignStructs_withAllAlignPanels() + { + String reply = super.alignStructs_withAllAlignPanels(); + if (reply != null) + { + statusBar.setText("Superposition failed: " + reply); + } + return reply; + } } diff --git a/src/jalview/gui/ColourMenuHelper.java b/src/jalview/gui/ColourMenuHelper.java index 31780d6..19ad939 100644 --- a/src/jalview/gui/ColourMenuHelper.java +++ b/src/jalview/gui/ColourMenuHelper.java @@ -3,6 +3,7 @@ package jalview.gui; import jalview.bin.Cache; import jalview.datamodel.AnnotatedCollectionI; import jalview.schemes.ColourSchemeI; +import jalview.schemes.ColourSchemeLoader; import jalview.schemes.ColourSchemes; import jalview.schemes.ResidueColourScheme; import jalview.schemes.UserColourScheme; @@ -258,7 +259,7 @@ public class ColourMenuHelper { try { - UserColourScheme ucs = ColourSchemes.loadColourScheme(file); + UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file); if (ucs != null && ColourSchemes.getInstance().nameExists(ucs.getName())) { diff --git a/src/jalview/gui/DasSourceBrowser.java b/src/jalview/gui/DasSourceBrowser.java index 8c8f228..c5ec067 100644 --- a/src/jalview/gui/DasSourceBrowser.java +++ b/src/jalview/gui/DasSourceBrowser.java @@ -453,7 +453,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements pane12.add(nametf, BorderLayout.EAST); panel.add(pane12, BorderLayout.NORTH); pane12 = new JPanel(new BorderLayout()); - pane12.add(new JLabel(MessageManager.getString("label.url")), + pane12.add(new JLabel(MessageManager.getString("label.url:")), BorderLayout.NORTH); pane12.add(seqs, BorderLayout.SOUTH); pane12.add(urltf, BorderLayout.EAST); diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 0321662..dc16a57 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -20,7 +20,6 @@ */ package jalview.gui; -import static jalview.util.UrlConstants.EMBLEBI_STRING; import static jalview.util.UrlConstants.SEQUENCE_ID; import jalview.api.AlignViewportI; @@ -39,11 +38,14 @@ import jalview.io.JalviewFileView; import jalview.jbgui.GSplitFrame; import jalview.jbgui.GStructureViewer; import jalview.structure.StructureSelectionManager; +import jalview.urls.IdOrgSettings; import jalview.util.ImageMaker; import jalview.util.MessageManager; import jalview.util.Platform; +import jalview.util.UrlConstants; import jalview.viewmodel.AlignmentViewport; import jalview.ws.params.ParamManager; +import jalview.ws.utils.UrlDownloadClient; import java.awt.BorderLayout; import java.awt.Color; @@ -78,6 +80,7 @@ import java.beans.PropertyChangeListener; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Hashtable; @@ -392,6 +395,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements showNews.setVisible(false); + getIdentifiersOrgData(); + checkURLLinks(); this.addWindowListener(new WindowAdapter() @@ -525,6 +530,29 @@ public class Desktop extends jalview.jbgui.GDesktop implements }); } + public void getIdentifiersOrgData() + { + // Thread off the identifiers fetcher + addDialogThread(new Runnable() + { + @Override + public void run() + { + Cache.log.debug("Downloading data from identifiers.org"); + UrlDownloadClient client = new UrlDownloadClient(); + try + { + client.download(IdOrgSettings.getUrl(), + IdOrgSettings.getDownloadLocation()); + } catch (IOException e) + { + Cache.log.debug("Exception downloading identifiers.org data" + + e.getMessage()); + } + } + }); + } + @Override protected void showNews_actionPerformed(ActionEvent e) { @@ -2290,7 +2318,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements { // check what the actual links are - if it's just the default don't // bother with the warning - Vector links = Preferences.sequenceURLLinks; + List links = Preferences.sequenceUrlLinks + .getLinksForMenu(); // only need to check links if there is one with a // SEQUENCE_ID which is not the default EMBL_EBI link @@ -2300,7 +2329,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements while (li.hasNext()) { String link = li.next(); - if (link.contains(SEQUENCE_ID) && !link.equals(EMBLEBI_STRING)) + if (link.contains(SEQUENCE_ID) + && !link.equals(UrlConstants.DEFAULT_STRING)) { check = true; int barPos = link.indexOf("|"); diff --git a/src/jalview/gui/FeatureRenderer.java b/src/jalview/gui/FeatureRenderer.java index ed6a3c5..f519f99 100644 --- a/src/jalview/gui/FeatureRenderer.java +++ b/src/jalview/gui/FeatureRenderer.java @@ -46,7 +46,6 @@ import java.util.Comparator; import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JLabel; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; @@ -61,8 +60,7 @@ import javax.swing.SwingConstants; * @version $Revision$ */ public class FeatureRenderer extends - jalview.renderer.seqfeatures.FeatureRenderer implements - jalview.api.FeatureRenderer + jalview.renderer.seqfeatures.FeatureRenderer { Color resBoxColour; @@ -339,9 +337,6 @@ public class FeatureRenderer extends if (reply == JvOptionPane.OK_OPTION && name.getText().length() > 0) { - // This ensures that the last sequence - // is refreshed and new features are rendered - lastSeq = null; lastFeatureAdded = name.getText().trim(); lastFeatureGroupAdded = source.getText().trim(); lastDescriptionAdded = description.getText().replaceAll("\n", " "); diff --git a/src/jalview/gui/IdCanvas.java b/src/jalview/gui/IdCanvas.java index 37be8bc..aad0776 100755 --- a/src/jalview/gui/IdCanvas.java +++ b/src/jalview/gui/IdCanvas.java @@ -21,6 +21,7 @@ package jalview.gui; import jalview.datamodel.SequenceI; +import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Color; @@ -158,33 +159,35 @@ public class IdCanvas extends JPanel return; } + ViewportRanges ranges = av.getRanges(); + gg.copyArea(0, 0, getWidth(), imgHeight, 0, -vertical * av.getCharHeight()); - int ss = av.startSeq; - int es = av.endSeq; + int ss = ranges.getStartSeq(); + int es = ranges.getEndSeq(); int transY = 0; if (vertical > 0) // scroll down { ss = es - vertical; - if (ss < av.startSeq) + if (ss < ranges.getStartSeq()) { // ie scrolling too fast, more than a page at a time - ss = av.startSeq; + ss = ranges.getStartSeq(); } else { - transY = imgHeight - (vertical * av.getCharHeight()); + transY = imgHeight - ((vertical + 1) * av.getCharHeight()); } } - else if (vertical < 0) + else if (vertical < 0) // scroll up { es = ss - vertical; - if (es > av.endSeq) + if (es > ranges.getEndSeq()) { - es = av.endSeq; + es = ranges.getEndSeq(); } } @@ -240,7 +243,7 @@ public class IdCanvas extends JPanel gg.setColor(Color.white); gg.fillRect(0, 0, getWidth(), imgHeight); - drawIds(av.getStartSeq(), av.endSeq); + drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq()); g.drawImage(image, 0, 0, this); } @@ -314,10 +317,11 @@ public class IdCanvas extends JPanel int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight; - int rowSize = av.getEndRes() - av.getStartRes(); + int rowSize = av.getRanges().getEndRes() + - av.getRanges().getStartRes(); // Draw the rest of the panels - for (int ypos = hgap, row = av.startRes; (ypos <= getHeight()) + for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getHeight()) && (row < maxwidth); ypos += cHeight, row += rowSize) { for (int i = starty; i < alheight; i++) @@ -354,7 +358,7 @@ public class IdCanvas extends JPanel SequenceI sequence; // Now draw the id strings - for (int i = starty; i < endy; i++) + for (int i = starty; i <= endy; i++) { sequence = av.getAlignment().getSequenceAt(i); diff --git a/src/jalview/gui/IdPanel.java b/src/jalview/gui/IdPanel.java index 59d12d9..2074900 100755 --- a/src/jalview/gui/IdPanel.java +++ b/src/jalview/gui/IdPanel.java @@ -27,7 +27,6 @@ import jalview.datamodel.SequenceI; import jalview.io.SequenceAnnotationReport; import jalview.util.MessageManager; import jalview.util.Platform; -import jalview.util.UrlLink; import jalview.viewmodel.AlignmentViewport; import java.awt.BorderLayout; @@ -37,9 +36,7 @@ import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.List; -import java.util.Vector; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; @@ -199,56 +196,10 @@ public class IdPanel extends JPanel implements MouseListener, return; } - Vector links = Preferences.sequenceURLLinks; - if (links == null || links.size() < 1) - { - return; - } - int seq = alignPanel.getSeqPanel().findSeq(e); - String url = null; - int i = 0; String id = av.getAlignment().getSequenceAt(seq).getName(); - while (url == null && i < links.size()) - { - // DEFAULT LINK IS FIRST IN THE LINK LIST - // BUT IF ITS A REGEX AND DOES NOT MATCH THE NEXT ONE WILL BE TRIED - url = links.elementAt(i++).toString(); - jalview.util.UrlLink urlLink = null; - try - { - urlLink = new UrlLink(url); - } catch (Exception foo) - { - jalview.bin.Cache.log.error("Exception for URLLink '" + url + "'", - foo); - url = null; - continue; - } + String url = Preferences.sequenceUrlLinks.getPrimaryUrl(id); - if (urlLink.usesDBAccession()) - { - // this URL requires an accession id, not the name of a sequence - url = null; - continue; - } - - if (!urlLink.isValid()) - { - jalview.bin.Cache.log.error(urlLink.getInvalidMessage()); - url = null; - continue; - } - - String urls[] = urlLink.makeUrls(id, true); - if (urls == null || urls[0] == null || urls[0].length() < 4) - { - url = null; - continue; - } - // just take first URL made from regex - url = urls[1]; - } try { jalview.util.BrowserLauncher.openURL(url); @@ -291,13 +242,14 @@ public class IdPanel extends JPanel implements MouseListener, return; } - if (mouseDragging && (e.getY() < 0) && (av.getStartSeq() > 0)) + if (mouseDragging && (e.getY() < 0) + && (av.getRanges().getStartSeq() > 0)) { scrollThread = new ScrollThread(true); } if (mouseDragging && (e.getY() >= getHeight()) - && (av.getAlignment().getHeight() > av.getEndSeq())) + && (av.getAlignment().getHeight() > av.getRanges().getEndSeq())) { scrollThread = new ScrollThread(false); } @@ -375,7 +327,7 @@ public class IdPanel extends JPanel implements MouseListener, Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq2); // build a new links menu based on the current links + any non-positional // features - Vector nlinks = new Vector(Preferences.sequenceURLLinks); + List nlinks = Preferences.sequenceUrlLinks.getLinksForMenu(); SequenceFeature sfs[] = sq == null ? null : sq.getSequenceFeatures(); if (sfs != null) { @@ -387,7 +339,7 @@ public class IdPanel extends JPanel implements MouseListener, { for (int l = 0, lSize = sf.links.size(); l < lSize; l++) { - nlinks.addElement(sf.links.elementAt(l)); + nlinks.add(sf.links.elementAt(l)); } } } @@ -491,9 +443,10 @@ public class IdPanel extends JPanel implements MouseListener, int index = av.getAlignment().findIndex(list.get(0)); // do we need to scroll the panel? - if ((av.getStartSeq() > index) || (av.getEndSeq() < index)) + if ((av.getRanges().getStartSeq() > index) + || (av.getRanges().getEndSeq() < index)) { - alignPanel.setScrollValues(av.getStartRes(), index); + alignPanel.setScrollValues(av.getRanges().getStartRes(), index); } } @@ -535,11 +488,11 @@ public class IdPanel extends JPanel implements MouseListener, if (alignPanel.scrollUp(up)) { // scroll was ok, so add new sequence to selection - int seq = av.getStartSeq(); + int seq = av.getRanges().getStartSeq(); if (!up) { - seq = av.getEndSeq(); + seq = av.getRanges().getEndSeq(); } if (seq < lastid) diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index 3ac453f..e8b832d 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -87,6 +87,7 @@ import jalview.util.Platform; import jalview.util.StringUtils; import jalview.util.jarInputStreamProvider; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import jalview.viewmodel.seqfeatures.FeatureRendererSettings; import jalview.viewmodel.seqfeatures.FeaturesDisplayed; import jalview.ws.jws2.Jws2Discoverer; @@ -755,6 +756,7 @@ public class Jalview2XML List userColours = new ArrayList(); AlignViewport av = ap.av; + ViewportRanges vpRanges = av.getRanges(); JalviewModel object = new JalviewModel(); object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel()); @@ -1270,8 +1272,8 @@ public class Jalview2XML view.setWidth(size.width); view.setHeight(size.height); - view.setStartRes(av.startRes); - view.setStartSeq(av.startSeq); + view.setStartRes(vpRanges.getStartRes()); + view.setStartSeq(vpRanges.getStartSeq()); if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme) { @@ -4448,8 +4450,8 @@ public class Jalview2XML af.viewport.setThresholdTextColour(view.getTextColThreshold()); af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view .isShowUnconserved() : false); - af.viewport.setStartRes(view.getStartRes()); - af.viewport.setStartSeq(view.getStartSeq()); + af.viewport.getRanges().setStartRes(view.getStartRes()); + af.viewport.getRanges().setStartSeq(view.getStartSeq()); af.alignPanel.updateLayout(); ColourSchemeI cs = null; // apply colourschemes diff --git a/src/jalview/gui/Jalview2XML_V1.java b/src/jalview/gui/Jalview2XML_V1.java index e751a2c..6235cbe 100755 --- a/src/jalview/gui/Jalview2XML_V1.java +++ b/src/jalview/gui/Jalview2XML_V1.java @@ -367,8 +367,8 @@ public class Jalview2XML_V1 af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(), view.getHeight()); - af.viewport.setStartRes(view.getStartRes()); - af.viewport.setStartSeq(view.getStartSeq()); + af.viewport.getRanges().setStartRes(view.getStartRes()); + af.viewport.getRanges().setStartSeq(view.getStartSeq()); af.viewport.setShowAnnotation(view.getShowAnnotation()); af.viewport.setAbovePIDThreshold(view.getPidSelected()); af.viewport.setColourText(view.getShowColourText()); diff --git a/src/jalview/gui/JalviewChimeraBindingModel.java b/src/jalview/gui/JalviewChimeraBindingModel.java index a797872..c9b35d8 100644 --- a/src/jalview/gui/JalviewChimeraBindingModel.java +++ b/src/jalview/gui/JalviewChimeraBindingModel.java @@ -28,13 +28,12 @@ import jalview.ext.rbvi.chimera.JalviewChimeraBinding; import jalview.io.DataSourceType; import jalview.structure.StructureSelectionManager; +import javax.swing.SwingUtilities; + public class JalviewChimeraBindingModel extends JalviewChimeraBinding { private ChimeraViewFrame cvf; - private FeatureRenderer fr = null; - - public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame, StructureSelectionManager ssm, PDBEntry[] pdbentry, SequenceI[][] sequenceIs, DataSourceType protocol) @@ -50,17 +49,10 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding : (AlignmentPanel) alignment; if (ap.av.isShowSequenceFeatures()) { - if (fr == null) - { - fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer(); - } - else - { - ap.updateFeatureRenderer(fr); - } + return ap.getSeqPanel().seqCanvas.fr; } - return fr; + return null; } @Override @@ -122,24 +114,25 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding protected void sendAsynchronousCommand(final String command, final String progressMsg) { - Thread thread = new Thread(new Runnable() + final long handle = progressMsg == null ? 0 : cvf + .startProgressBar(progressMsg); + SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - long stm = cvf.startProgressBar(progressMsg); try { sendChimeraCommand(command, false); } finally { - cvf.stopProgressBar(null, stm); + if (progressMsg != null) + { + cvf.stopProgressBar(null, handle); + } } } }); - thread.start(); - } @Override diff --git a/src/jalview/gui/OverviewPanel.java b/src/jalview/gui/OverviewPanel.java index 1c48690..c530fdc 100755 --- a/src/jalview/gui/OverviewPanel.java +++ b/src/jalview/gui/OverviewPanel.java @@ -20,7 +20,10 @@ */ package jalview.gui; +import jalview.datamodel.SequenceI; import jalview.renderer.AnnotationRenderer; +import jalview.renderer.seqfeatures.FeatureColourFinder; +import jalview.viewmodel.OverviewDimensions; import java.awt.Color; import java.awt.Dimension; @@ -35,57 +38,51 @@ import java.awt.image.BufferedImage; import javax.swing.JPanel; /** - * DOCUMENT ME! + * Panel displaying an overview of the full alignment, with an interactive box + * representing the viewport onto the alignment. * * @author $author$ * @version $Revision$ */ public class OverviewPanel extends JPanel implements Runnable { - BufferedImage miniMe; + private static final Color TRANS_GREY = new Color(100, 100, 100, 25); - AlignViewport av; + private final AnnotationRenderer renderer = new AnnotationRenderer(); - AlignmentPanel ap; + private OverviewDimensions od; - final AnnotationRenderer renderer = new AnnotationRenderer(); + private BufferedImage miniMe; - float scalew = 1f; - - float scaleh = 1f; - - int width; - - int sequencesHeight; - - int graphHeight = 20; - - int boxX = -1; + private BufferedImage lastMiniMe = null; - int boxY = -1; + private AlignViewport av; - int boxWidth = -1; + private AlignmentPanel ap; - int boxHeight = -1; + // + private boolean resizing = false; - boolean resizing = false; + // This is set true if the user resizes whilst + // the overview is being calculated + private boolean resizeAgain = false; // Can set different properties in this seqCanvas than // main visible SeqCanvas - SequenceRenderer sr; + private SequenceRenderer sr; jalview.renderer.seqfeatures.FeatureRenderer fr; /** * Creates a new OverviewPanel object. * - * @param ap - * DOCUMENT ME! + * @param alPanel + * The alignment panel which is shown in the overview panel */ - public OverviewPanel(AlignmentPanel ap) + public OverviewPanel(AlignmentPanel alPanel) { - this.av = ap.av; - this.ap = ap; + this.av = alPanel.av; + this.ap = alPanel; setLayout(null); sr = new SequenceRenderer(av); @@ -93,44 +90,17 @@ public class OverviewPanel extends JPanel implements Runnable sr.forOverview = true; fr = new FeatureRenderer(ap); - // scale the initial size of overviewpanel to shape of alignment - float initialScale = (float) av.getAlignment().getWidth() - / (float) av.getAlignment().getHeight(); - - if (av.getAlignmentConservationAnnotation() == null) - { - graphHeight = 0; - } - - if (av.getAlignment().getWidth() > av.getAlignment().getHeight()) - { - // wider - width = 400; - sequencesHeight = (int) (400f / initialScale); - if (sequencesHeight < 40) - { - sequencesHeight = 40; - } - } - else - { - // taller - width = (int) (400f * initialScale); - sequencesHeight = 300; - - if (width < 120) - { - width = 120; - } - } + od = new OverviewDimensions(av.getRanges(), + (av.isShowAnnotation() && av + .getAlignmentConservationAnnotation() != null)); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent evt) { - if ((getWidth() != width) - || (getHeight() != (sequencesHeight + graphHeight))) + if ((getWidth() != od.getWidth()) + || (getHeight() != (od.getHeight()))) { updateOverviewImage(); } @@ -144,11 +114,10 @@ public class OverviewPanel extends JPanel implements Runnable { if (!av.getWrapAlignment()) { - // TODO: feature: jv2.5 detect shift drag and update selection from - // it. - boxX = evt.getX(); - boxY = evt.getY(); - checkValid(); + od.updateViewportFromMouse(evt.getX(), evt.getY(), av + .getAlignment().getHiddenSequences(), av + .getColumnSelection(), av.getRanges()); + ap.setScrollValues(od.getScrollCol(), od.getScrollRow()); } } }); @@ -160,9 +129,10 @@ public class OverviewPanel extends JPanel implements Runnable { if (!av.getWrapAlignment()) { - boxX = evt.getX(); - boxY = evt.getY(); - checkValid(); + od.updateViewportFromMouse(evt.getX(), evt.getY(), av + .getAlignment().getHiddenSequences(), av + .getColumnSelection(), av.getRanges()); + ap.setScrollValues(od.getScrollCol(), od.getScrollRow()); } } }); @@ -171,60 +141,7 @@ public class OverviewPanel extends JPanel implements Runnable } /** - * DOCUMENT ME! - */ - void checkValid() - { - if (boxY < 0) - { - boxY = 0; - } - - if (boxY > (sequencesHeight - boxHeight)) - { - boxY = sequencesHeight - boxHeight + 1; - } - - if (boxX < 0) - { - boxX = 0; - } - - if (boxX > (width - boxWidth)) - { - if (av.hasHiddenColumns()) - { - // Try smallest possible box - boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew); - } - boxX = width - boxWidth; - } - - int col = (int) (boxX / scalew / av.getCharWidth()); - int row = (int) (boxY / scaleh / av.getCharHeight()); - - if (av.hasHiddenColumns()) - { - if (!av.getColumnSelection().isVisible(col)) - { - return; - } - - col = av.getColumnSelection().findColumnPosition(col); - } - - if (av.hasHiddenRows()) - { - row = av.getAlignment().getHiddenSequences() - .findIndexWithoutHiddenSeqs(row); - } - - ap.setScrollValues(col, row); - - } - - /** - * DOCUMENT ME! + * Updates the overview image when the related alignment panel is updated */ public void updateOverviewImage() { @@ -238,24 +155,17 @@ public class OverviewPanel extends JPanel implements Runnable if ((getWidth() > 0) && (getHeight() > 0)) { - width = getWidth(); - sequencesHeight = getHeight() - graphHeight; + od.setWidth(getWidth()); + od.setHeight(getHeight()); } - setPreferredSize(new Dimension(width, sequencesHeight + graphHeight)); + setPreferredSize(new Dimension(od.getWidth(), od.getHeight())); Thread thread = new Thread(this); thread.start(); repaint(); } - // This is set true if the user resizes whilst - // the overview is being calculated - boolean resizeAgain = false; - - /** - * DOCUMENT ME! - */ @Override public void run() { @@ -266,148 +176,47 @@ public class OverviewPanel extends JPanel implements Runnable fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer()); } - int alwidth = av.getAlignment().getWidth(); - int alheight = av.getAlignment().getHeight() - + av.getAlignment().getHiddenSequences().getSize(); - - setPreferredSize(new Dimension(width, sequencesHeight + graphHeight)); - - int fullsizeWidth = alwidth * av.getCharWidth(); - int fullsizeHeight = alheight * av.getCharHeight(); + // why do we need to set preferred size again? was set in + // updateOverviewImage + setPreferredSize(new Dimension(od.getWidth(), od.getHeight())); - scalew = (float) width / (float) fullsizeWidth; - scaleh = (float) sequencesHeight / (float) fullsizeHeight; - - miniMe = new BufferedImage(width, sequencesHeight + graphHeight, + miniMe = new BufferedImage(od.getWidth(), od.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics mg = miniMe.getGraphics(); mg.setColor(Color.orange); - mg.fillRect(0, 0, width, miniMe.getHeight()); - - float sampleCol = (float) alwidth / (float) width; - float sampleRow = (float) alheight / (float) sequencesHeight; + mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight()); - int lastcol = -1, lastrow = -1; - int color = Color.white.getRGB(); - int row, col; - jalview.datamodel.SequenceI seq; - final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av - .hasHiddenColumns(); - boolean hiddenRow = false; - // get hidden row and hidden column map once at beginning. - // clone featureRenderer settings to avoid race conditions... if state is - // updated just need to refresh again - for (row = 0; row < sequencesHeight; row++) - { - if (resizeAgain) - { - break; - } - if ((int) (row * sampleRow) == lastrow) - { - // No need to recalculate the colours, - // Just copy from the row above - for (col = 0; col < width; col++) - { - if (resizeAgain) - { - break; - } - miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1)); - } - continue; - } - - lastrow = (int) (row * sampleRow); - - hiddenRow = false; - if (hasHiddenRows) - { - seq = av.getAlignment().getHiddenSequences() - .getHiddenSequence(lastrow); - if (seq == null) - { - int index = av.getAlignment().getHiddenSequences() - .findIndexWithoutHiddenSeqs(lastrow); - - seq = av.getAlignment().getSequenceAt(index); - } - else - { - hiddenRow = true; - } - } - else - { - seq = av.getAlignment().getSequenceAt(lastrow); - } - - if (seq == null) - { - System.out.println(lastrow + " null"); - continue; - } - - for (col = 0; col < width; col++) - { - if (resizeAgain) - { - break; - } - if ((int) (col * sampleCol) == lastcol - && (int) (row * sampleRow) == lastrow) - { - miniMe.setRGB(col, row, color); - continue; - } - - lastcol = (int) (col * sampleCol); - - if (seq.getLength() > lastcol) - { - color = sr.getResidueBoxColour(seq, lastcol).getRGB(); - - if (av.isShowSequenceFeatures()) - { - color = fr.findFeatureColour(color, seq, lastcol); - } - } - else - { - color = -1; // White - } + // calculate sampleCol and sampleRow + // alignment width is max number of residues/bases + // alignment height is number of sequences + int alwidth = av.getAlignment().getWidth(); + int alheight = av.getAlignment().getAbsoluteHeight(); - if (hiddenRow - || (hasHiddenCols && !av.getColumnSelection().isVisible( - lastcol))) - { - color = new Color(color).darker().darker().getRGB(); - } + // sampleCol or sampleRow is the width/height allocated to each residue + // in particular, sometimes we may need more than one row/col of the + // BufferedImage allocated + // sampleCol is how much of a residue to assign to each pixel + // sampleRow is how many sequences to assign to each pixel + float sampleCol = alwidth / (float) od.getWidth(); + float sampleRow = alheight / (float) od.getSequencesHeight(); - miniMe.setRGB(col, row, color); + buildImage(sampleRow, sampleCol); - } - } - - if (av.getAlignmentConservationAnnotation() != null) + // check for conservation annotation to make sure overview works for DNA too + if (av.isShowAnnotation() + && (av.getAlignmentConservationAnnotation() != null)) { renderer.updateFromAlignViewport(av); - for (col = 0; col < width; col++) + for (int col = 0; col < od.getWidth() && !resizeAgain; col++) { - if (resizeAgain) - { - break; - } - lastcol = (int) (col * sampleCol); - { - mg.translate(col, sequencesHeight); - renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(), - av.getAlignmentConservationAnnotation().annotations, - (int) (sampleCol) + 1, graphHeight, - (int) (col * sampleCol), (int) (col * sampleCol) + 1); - mg.translate(-col, -sequencesHeight); - } + mg.translate(col, od.getSequencesHeight()); + renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(), + av.getAlignmentConservationAnnotation().annotations, + (int) (sampleCol) + 1, od.getGraphHeight(), + (int) (col * sampleCol), (int) (col * sampleCol) + 1); + mg.translate(-col, -od.getSequencesHeight()); + } } System.gc(); @@ -427,66 +236,97 @@ public class OverviewPanel extends JPanel implements Runnable setBoxPosition(); } - /** - * DOCUMENT ME! + /* + * Build the overview panel image */ - public void setBoxPosition() + private void buildImage(float sampleRow, float sampleCol) { - int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth(); - int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment() - .getHiddenSequences().getSize()) - * av.getCharHeight(); + int lastcol = -1; + int lastrow = -1; + int rgbColour = Color.white.getRGB(); - int startRes = av.getStartRes(); - int endRes = av.getEndRes(); + SequenceI seq = null; + FeatureColourFinder finder = new FeatureColourFinder(fr); - if (av.hasHiddenColumns()) + final boolean hasHiddenCols = av.hasHiddenColumns(); + boolean hiddenRow = false; + // get hidden row and hidden column map once at beginning. + // clone featureRenderer settings to avoid race conditions... if state is + // updated just need to refresh again + for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++) { - startRes = av.getColumnSelection().adjustForHiddenColumns(startRes); - endRes = av.getColumnSelection().adjustForHiddenColumns(endRes); - } + boolean doCopy = true; + int currentrow = (int) (row * sampleRow); + if (currentrow != lastrow) + { + doCopy = false; - int startSeq = av.startSeq; - int endSeq = av.endSeq; + lastrow = currentrow; - if (av.hasHiddenRows()) - { - startSeq = av.getAlignment().getHiddenSequences() - .adjustForHiddenSeqs(startSeq); + // get the sequence which would be at alignment index 'lastrow' if no + // rows were hidden, and determine whether it is hidden or not + hiddenRow = av.getAlignment().isHidden(lastrow); + seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow); + } - endSeq = av.getAlignment().getHiddenSequences() - .adjustForHiddenSeqs(endSeq); + for (int col = 0; col < od.getWidth() && !resizeAgain; col++) + { + if (doCopy) + { + rgbColour = miniMe.getRGB(col, row - 1); + } + else if ((int) (col * sampleCol) != lastcol + || (int) (row * sampleRow) != lastrow) + { + lastcol = (int) (col * sampleCol); + rgbColour = getColumnColourFromSequence(seq, hiddenRow, + hasHiddenCols, lastcol, finder); + } + // else we just use the color we already have , so don't need to set it + miniMe.setRGB(col, row, rgbColour); + } } + } - scalew = (float) width / (float) fullsizeWidth; - scaleh = (float) sequencesHeight / (float) fullsizeHeight; - - boxX = (int) (startRes * av.getCharWidth() * scalew); - boxY = (int) (startSeq * av.getCharHeight() * scaleh); + /* + * Find the colour of a sequence at a specified column position + */ + private int getColumnColourFromSequence( + jalview.datamodel.SequenceI seq, + boolean hiddenRow, boolean hasHiddenCols, int lastcol, + FeatureColourFinder finder) + { + Color color = Color.white; - if (av.hasHiddenColumns()) + if ((seq != null) && (seq.getLength() > lastcol)) { - boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew); + color = sr.getResidueColour(seq, lastcol, finder); } - else + + if (hiddenRow + || (hasHiddenCols && !av.getColumnSelection() + .isVisible(lastcol))) { - boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew); + color = color.darker().darker(); } - boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh); - - repaint(); + return color.getRGB(); } - private BufferedImage lastMiniMe = null; - /** - * DOCUMENT ME! + * Update the overview panel box when the associated alignment panel is + * changed * - * @param g - * DOCUMENT ME! */ + public void setBoxPosition() + { + od.setBoxPosition(av.getAlignment() + .getHiddenSequences(), av.getColumnSelection(), av.getRanges()); + repaint(); + } + + @Override public void paintComponent(Graphics g) { @@ -501,7 +341,7 @@ public class OverviewPanel extends JPanel implements Runnable { g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); } - g.setColor(new Color(100, 100, 100, 25)); + g.setColor(TRANS_GREY); g.fillRect(0, 0, getWidth(), getHeight()); } else if (lastMiniMe != null) @@ -509,13 +349,12 @@ public class OverviewPanel extends JPanel implements Runnable g.drawImage(lastMiniMe, 0, 0, this); if (lastMiniMe != miniMe) { - g.setColor(new Color(100, 100, 100, 25)); + g.setColor(TRANS_GREY); g.fillRect(0, 0, getWidth(), getHeight()); } } - // TODO: render selected regions + g.setColor(Color.red); - g.drawRect(boxX, boxY, boxWidth, boxHeight); - g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2); + od.drawBox(g); } } diff --git a/src/jalview/gui/Preferences.java b/src/jalview/gui/Preferences.java index d32ff46..cf80a6d 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -20,11 +20,6 @@ */ package jalview.gui; -import static jalview.util.UrlConstants.DB_ACCESSION; -import static jalview.util.UrlConstants.EMBLEBI_STRING; -import static jalview.util.UrlConstants.SEQUENCE_ID; -import static jalview.util.UrlConstants.SRS_STRING; - import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; import jalview.bin.Cache; import jalview.gui.Help.HelpId; @@ -37,12 +32,18 @@ import jalview.jbgui.GSequenceLink; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemes; import jalview.schemes.ResidueColourScheme; +import jalview.urls.UrlLinkTableModel; +import jalview.urls.api.UrlProviderFactoryI; +import jalview.urls.api.UrlProviderI; +import jalview.urls.desktop.DesktopUrlProviderFactory; import jalview.util.MessageManager; import jalview.util.Platform; +import jalview.util.UrlConstants; import jalview.ws.sifts.SiftsSettings; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; @@ -51,14 +52,24 @@ import java.awt.event.MouseEvent; import java.io.File; import java.util.ArrayList; import java.util.List; -import java.util.StringTokenizer; -import java.util.Vector; import javax.help.HelpSetException; import javax.swing.JColorChooser; import javax.swing.JFileChooser; import javax.swing.JInternalFrame; import javax.swing.JPanel; +import javax.swing.ListSelectionModel; +import javax.swing.RowFilter; +import javax.swing.RowSorter; +import javax.swing.SortOrder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; import ext.edu.ucsf.rbvi.strucviz2.StructureManager; @@ -104,7 +115,9 @@ public class Preferences extends GPreferences * Holds name and link separated with | character. Sequence ID must be * $SEQUENCE_ID$ or $SEQUENCE_ID=/.possible | chars ./=$ */ - public static Vector sequenceURLLinks; + public static UrlProviderI sequenceUrlLinks; + + public static UrlLinkTableModel dataModel; /** * Holds name and link separated with | character. Sequence IDS and Sequences @@ -117,40 +130,23 @@ public class Preferences extends GPreferences public static List groupURLLinks; static { - String string = Cache.getDefault("SEQUENCE_LINKS", EMBLEBI_STRING); - sequenceURLLinks = new Vector(); - - try + // get links selected to be in the menu (SEQUENCE_LINKS) + // and links entered by the user but not selected (STORED_LINKS) + String inMenuString = Cache.getDefault("SEQUENCE_LINKS", ""); + String notInMenuString = Cache.getDefault("STORED_LINKS", ""); + String defaultUrl = Cache.getDefault("DEFAULT_URL", + UrlConstants.DEFAULT_LABEL); + + // if both links lists are empty, add the DEFAULT_URL link + // otherwise we assume the default link is in one of the lists + if (inMenuString.isEmpty() && notInMenuString.isEmpty()) { - StringTokenizer st = new StringTokenizer(string, "|"); - while (st.hasMoreElements()) - { - String name = st.nextToken(); - String url = st.nextToken(); - // check for '|' within a regex - int rxstart = url.indexOf("$" + DB_ACCESSION + "$"); - if (rxstart == -1) - { - rxstart = url.indexOf("$" + SEQUENCE_ID + "$"); - } - while (rxstart == -1 && url.indexOf("/=$") == -1) - { - url = url + "|" + st.nextToken(); - } - sequenceURLLinks.addElement(name + "|" + url); - } - } catch (Exception ex) - { - System.out.println(ex + "\nError parsing sequence links"); - } - { - // upgrade old SRS link - int srsPos = sequenceURLLinks.indexOf(SRS_STRING); - if (srsPos > -1) - { - sequenceURLLinks.setElementAt(EMBLEBI_STRING, srsPos); - } + inMenuString = UrlConstants.DEFAULT_STRING; } + UrlProviderFactoryI factory = new DesktopUrlProviderFactory(defaultUrl, + inMenuString, notInMenuString); + sequenceUrlLinks = factory.createUrlProvider(); + dataModel = new UrlLinkTableModel(sequenceUrlLinks); /** * TODO: reformulate groupURL encoding so two or more can be stored in the @@ -160,8 +156,6 @@ public class Preferences extends GPreferences groupURLLinks = new ArrayList(); } - Vector nameLinks, urlLinks; - JInternalFrame frame; DasSourceBrowser dasSource; @@ -349,20 +343,128 @@ public class Preferences extends GPreferences /* * Set Connections tab defaults */ - nameLinks = new Vector(); - urlLinks = new Vector(); - for (int i = 0; i < sequenceURLLinks.size(); i++) + + // set up sorting + linkUrlTable.setModel(dataModel); + final TableRowSorter sorter = new TableRowSorter<>( + linkUrlTable.getModel()); + linkUrlTable.setRowSorter(sorter); + List sortKeys = new ArrayList<>(); + + UrlLinkTableModel m = (UrlLinkTableModel) linkUrlTable.getModel(); + sortKeys.add(new RowSorter.SortKey(m.getPrimaryColumn(), + SortOrder.DESCENDING)); + sortKeys.add(new RowSorter.SortKey(m.getSelectedColumn(), + SortOrder.DESCENDING)); + sortKeys.add(new RowSorter.SortKey(m.getNameColumn(), + SortOrder.ASCENDING)); + + sorter.setSortKeys(sortKeys); + sorter.sort(); + + // set up filtering + ActionListener onReset; + onReset = new ActionListener() { - String link = sequenceURLLinks.elementAt(i).toString(); - nameLinks.addElement(link.substring(0, link.indexOf("|"))); - urlLinks.addElement(link.substring(link.indexOf("|") + 1)); - } + @Override + public void actionPerformed(ActionEvent e) + { + filterTB.setText(""); + sorter.setRowFilter(RowFilter.regexFilter("")); + } + + }; + doReset.addActionListener(onReset); + + // filter to display only custom urls + final RowFilter customUrlFilter = new RowFilter() + { + @Override + public boolean include( + Entry entry) + { + return ((UrlLinkTableModel) entry.getModel()).isUserEntry(entry); + } + }; + + final TableRowSorter customSorter = new TableRowSorter<>( + linkUrlTable.getModel()); + customSorter.setRowFilter(customUrlFilter); + + ActionListener onCustomOnly; + onCustomOnly = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + filterTB.setText(""); + sorter.setRowFilter(customUrlFilter); + } + }; + userOnly.addActionListener(onCustomOnly); + + filterTB.getDocument().addDocumentListener(new DocumentListener() + { + String caseInsensitiveFlag = "(?i)"; - updateLinkData(); + @Override + public void changedUpdate(DocumentEvent e) + { + sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag + + filterTB.getText())); + } + + @Override + public void removeUpdate(DocumentEvent e) + { + sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag + + filterTB.getText())); + } + + @Override + public void insertUpdate(DocumentEvent e) + { + sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag + + filterTB.getText())); + } + }); + + // set up list selection functionality + linkUrlTable.getSelectionModel().addListSelectionListener( + new UrlListSelectionHandler()); + + // set up radio buttons + int onClickCol = ((UrlLinkTableModel) linkUrlTable.getModel()) + .getPrimaryColumn(); + String onClickName = linkUrlTable.getColumnName(onClickCol); + linkUrlTable.getColumn(onClickName).setCellRenderer( + new RadioButtonRenderer()); + linkUrlTable.getColumn(onClickName) + .setCellEditor(new RadioButtonEditor()); + + // get boolean columns and resize those to min possible + for (int column = 0; column < linkUrlTable.getColumnCount(); column++) + { + if (linkUrlTable.getModel().getColumnClass(column) + .equals(Boolean.class)) + { + TableColumn tableColumn = linkUrlTable.getColumnModel().getColumn( + column); + int preferredWidth = tableColumn.getMinWidth(); + + TableCellRenderer cellRenderer = linkUrlTable.getCellRenderer(0, + column); + Component c = linkUrlTable.prepareRenderer(cellRenderer, 0, column); + int cwidth = c.getPreferredSize().width + + linkUrlTable.getIntercellSpacing().width; + preferredWidth = Math.max(preferredWidth, cwidth); + + tableColumn.setPreferredWidth(preferredWidth); + } + } useProxy.setSelected(Cache.getDefault("USE_PROXY", false)); - proxyServerTB.setEnabled(useProxy.isSelected()); - proxyPortTB.setEnabled(useProxy.isSelected()); + useProxy_actionPerformed(); // make sure useProxy is correctly initialised proxyServerTB.setText(Cache.getDefault("PROXY_SERVER", "")); proxyPortTB.setText(Cache.getDefault("PROXY_PORT", "")); @@ -554,28 +656,32 @@ public class Preferences extends GPreferences jalview.util.BrowserLauncher.resetBrowser(); - if (nameLinks.size() > 0) + // save user-defined and selected links + String menuLinks = sequenceUrlLinks.writeUrlsAsString(true); + if (menuLinks.isEmpty()) + { + Cache.applicationProperties.remove("SEQUENCE_LINKS"); + } + else { - StringBuffer links = new StringBuffer(); - sequenceURLLinks = new Vector(); - for (int i = 0; i < nameLinks.size(); i++) - { - sequenceURLLinks.addElement(nameLinks.elementAt(i) + "|" - + urlLinks.elementAt(i)); - links.append(sequenceURLLinks.elementAt(i).toString()); - links.append("|"); - } - // remove last "|" - links.setLength(links.length() - 1); Cache.applicationProperties.setProperty("SEQUENCE_LINKS", - links.toString()); + menuLinks.toString()); + } + + String nonMenuLinks = sequenceUrlLinks.writeUrlsAsString(false); + if (nonMenuLinks.isEmpty()) + { + Cache.applicationProperties.remove("STORED_LINKS"); } else { - Cache.applicationProperties.remove("SEQUENCE_LINKS"); - sequenceURLLinks.clear(); + Cache.applicationProperties.setProperty("STORED_LINKS", + nonMenuLinks.toString()); } + Cache.applicationProperties.setProperty("DEFAULT_URL", + sequenceUrlLinks.getPrimaryUrlId()); + Cache.applicationProperties.setProperty("USE_PROXY", Boolean.toString(useProxy.isSelected())); @@ -756,7 +862,6 @@ public class Preferences extends GPreferences @Override public void newLink_actionPerformed(ActionEvent e) { - GSequenceLink link = new GSequenceLink(); boolean valid = false; while (!valid) @@ -767,10 +872,18 @@ public class Preferences extends GPreferences { if (link.checkValid()) { - nameLinks.addElement(link.getName()); - urlLinks.addElement(link.getURL()); - updateLinkData(); - valid = true; + if (((UrlLinkTableModel) linkUrlTable.getModel()) + .isUniqueName(link.getName())) + { + ((UrlLinkTableModel) linkUrlTable.getModel()).insertRow( + link.getName(), link.getURL()); + valid = true; + } + else + { + link.notifyDuplicate(); + continue; + } } } else @@ -785,36 +898,46 @@ public class Preferences extends GPreferences { GSequenceLink link = new GSequenceLink(); - int index = linkNameList.getSelectedIndex(); + int index = linkUrlTable.getSelectedRow(); if (index == -1) { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, - MessageManager.getString("label.no_link_selected"), - MessageManager.getString("label.no_link_selected"), - JvOptionPane.WARNING_MESSAGE); + // button no longer enabled if row is not selected + Cache.log.debug("Edit with no row selected in linkUrlTable"); return; } - link.setName(nameLinks.elementAt(index).toString()); - link.setURL(urlLinks.elementAt(index).toString()); + int nameCol = ((UrlLinkTableModel) linkUrlTable.getModel()) + .getNameColumn(); + int urlCol = ((UrlLinkTableModel) linkUrlTable.getModel()) + .getUrlColumn(); + String oldName = linkUrlTable.getValueAt(index, nameCol).toString(); + link.setName(oldName); + link.setURL(linkUrlTable.getValueAt(index, urlCol).toString()); boolean valid = false; while (!valid) { - if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link, - MessageManager.getString("label.new_sequence_url_link"), + MessageManager.getString("label.edit_sequence_url_link"), JvOptionPane.OK_CANCEL_OPTION, -1, null) == JvOptionPane.OK_OPTION) { if (link.checkValid()) { - nameLinks.setElementAt(link.getName(), index); - urlLinks.setElementAt(link.getURL(), index); - updateLinkData(); - valid = true; + if ((oldName.equals(link.getName())) + || (((UrlLinkTableModel) linkUrlTable.getModel()) + .isUniqueName(link.getName()))) + { + linkUrlTable.setValueAt(link.getName(), index, nameCol); + linkUrlTable.setValueAt(link.getURL(), index, urlCol); + valid = true; + } + else + { + link.notifyDuplicate(); + continue; + } } } - else { break; @@ -825,26 +948,24 @@ public class Preferences extends GPreferences @Override public void deleteLink_actionPerformed(ActionEvent e) { - int index = linkNameList.getSelectedIndex(); + int index = linkUrlTable.getSelectedRow(); + int modelIndex = -1; if (index == -1) { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, - MessageManager.getString("label.no_link_selected"), - MessageManager.getString("label.no_link_selected"), - JvOptionPane.WARNING_MESSAGE); + // button no longer enabled if row is not selected + Cache.log.debug("Delete with no row selected in linkUrlTable"); return; } - nameLinks.removeElementAt(index); - urlLinks.removeElementAt(index); - updateLinkData(); - } + else + { + modelIndex = linkUrlTable.convertRowIndexToModel(index); + } - void updateLinkData() - { - linkNameList.setListData(nameLinks); - linkURLList.setListData(urlLinks); + // make sure we use the model index to delete, and not the table index + ((UrlLinkTableModel) linkUrlTable.getModel()).removeRow(modelIndex); } + @Override public void defaultBrowser_mouseClicked(MouseEvent e) { @@ -1061,4 +1182,45 @@ public class Preferences extends GPreferences return name.hashCode() + code.hashCode(); } } + + private class UrlListSelectionHandler implements ListSelectionListener + { + + @Override + public void valueChanged(ListSelectionEvent e) + { + ListSelectionModel lsm = (ListSelectionModel) e.getSource(); + + int index = lsm.getMinSelectionIndex(); + if (index == -1) + { + // no selection, so disable delete/edit buttons + editLink.setEnabled(false); + deleteLink.setEnabled(false); + return; + } + int modelIndex = linkUrlTable.convertRowIndexToModel(index); + + // enable/disable edit and delete link buttons + if (((UrlLinkTableModel) linkUrlTable.getModel()) + .isRowDeletable(modelIndex)) + { + deleteLink.setEnabled(true); + } + else + { + deleteLink.setEnabled(false); + } + + if (((UrlLinkTableModel) linkUrlTable.getModel()) + .isRowEditable(modelIndex)) + { + editLink.setEnabled(true); + } + else + { + editLink.setEnabled(false); + } + } +} } diff --git a/src/jalview/gui/ScalePanel.java b/src/jalview/gui/ScalePanel.java index 8961f21..de21be6 100755 --- a/src/jalview/gui/ScalePanel.java +++ b/src/jalview/gui/ScalePanel.java @@ -101,7 +101,7 @@ public class ScalePanel extends JPanel implements MouseMotionListener, @Override public void mousePressed(MouseEvent evt) { - int x = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes(); final int res; if (av.hasHiddenColumns()) @@ -282,7 +282,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener, { mouseDragging = false; - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int res = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); if (av.hasHiddenColumns()) { @@ -337,7 +338,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener, mouseDragging = true; ColumnSelection cs = av.getColumnSelection(); - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int res = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); res = Math.max(0, res); res = cs.adjustForHiddenColumns(res); res = Math.min(res, av.getAlignment().getWidth() - 1); @@ -389,7 +391,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener, return; } - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); + int res = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); res = av.getColumnSelection().adjustForHiddenColumns(res); @@ -419,7 +422,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener, @Override public void paintComponent(Graphics g) { - drawScale(g, av.getStartRes(), av.getEndRes(), getWidth(), getHeight()); + drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(), + getWidth(), getHeight()); } // scalewidth will normally be screenwidth, diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index d015292..4557819 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -26,6 +26,7 @@ import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.ScaleRenderer; import jalview.renderer.ScaleRenderer.ScaleMark; +import jalview.viewmodel.ViewportRanges; import java.awt.BasicStroke; import java.awt.BorderLayout; @@ -279,10 +280,11 @@ public class SeqCanvas extends JComponent gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth, imgHeight, -horizontal * charWidth, -vertical * charHeight); - int sr = av.startRes; - int er = av.endRes; - int ss = av.startSeq; - int es = av.endSeq; + ViewportRanges ranges = av.getRanges(); + int sr = ranges.getStartRes(); + int er = ranges.getEndRes(); + int ss = ranges.getStartSeq(); + int es = ranges.getEndSeq(); int transX = 0; int transY = 0; @@ -300,22 +302,22 @@ public class SeqCanvas extends JComponent { ss = es - vertical; - if (ss < av.startSeq) + if (ss < ranges.getStartSeq()) { // ie scrolling too fast, more than a page at a time - ss = av.startSeq; + ss = ranges.getStartSeq(); } else { - transY = imgHeight - (vertical * charHeight); + transY = imgHeight - ((vertical + 1) * charHeight); } } else if (vertical < 0) { es = ss - vertical; - if (es > av.endSeq) + if (es > ranges.getEndSeq()) { - es = av.endSeq; + es = ranges.getEndSeq(); } } @@ -395,13 +397,15 @@ public class SeqCanvas extends JComponent gg.setColor(Color.white); gg.fillRect(0, 0, imgWidth, imgHeight); + ViewportRanges ranges = av.getRanges(); if (av.getWrapAlignment()) { - drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes); + drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes()); } else { - drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0); + drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(), + ranges.getStartSeq(), ranges.getEndSeq(), 0); } g.drawImage(lcimg, 0, 0, this); @@ -503,7 +507,7 @@ public class SeqCanvas extends JComponent av.setWrappedWidth(cWidth); - av.endRes = av.startRes + cWidth; + av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth); int endx; int ypos = hgap; @@ -718,7 +722,7 @@ public class SeqCanvas extends JComponent // / First draw the sequences // /////////////////////////// - for (int i = startSeq; i < endSeq; i++) + for (int i = startSeq; i <= endSeq; i++) { nextSeq = av.getAlignment().getSequenceAt(i); if (nextSeq == null) @@ -733,7 +737,7 @@ public class SeqCanvas extends JComponent if (av.isShowSequenceFeatures()) { fr.drawSequence(g, nextSeq, startRes, endRes, offset - + ((i - startSeq) * charHeight)); + + ((i - startSeq) * charHeight), false); } // / Highlight search Results once all sequences have been drawn @@ -802,7 +806,7 @@ public class SeqCanvas extends JComponent int top = -1; int bottom = -1; - for (i = startSeq; i < endSeq; i++) + for (i = startSeq; i <= endSeq; i++) { sx = (group.getStartRes() - startRes) * charWidth; sy = offset + ((i - startSeq) * charHeight); diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index e402d28..db7aa36 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -209,7 +209,7 @@ public class SeqPanel extends JPanel implements MouseListener, } wrappedBlock = y / cHeight; - wrappedBlock += av.getStartRes() / cwidth; + wrappedBlock += av.getRanges().getStartRes() / cwidth; res = wrappedBlock * cwidth + x / av.getCharWidth(); @@ -222,11 +222,11 @@ public class SeqPanel extends JPanel implements MouseListener, // right-hand gutter x = seqCanvas.getX() + seqCanvas.getWidth(); } - res = (x / av.getCharWidth()) + av.getStartRes(); - if (res > av.getEndRes()) + res = (x / av.getCharWidth()) + av.getRanges().getStartRes(); + if (res > av.getRanges().getEndRes()) { // moused off right - res = av.getEndRes(); + res = av.getRanges().getEndRes(); } } @@ -262,7 +262,9 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av + seq = Math.min((y / av.getCharHeight()) + + av.getRanges().getStartSeq(), + av .getAlignment().getHeight() - 1); } @@ -385,18 +387,18 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - while (seqCanvas.cursorY < av.startSeq) + while (seqCanvas.cursorY < av.getRanges().getStartSeq()) { ap.scrollUp(true); } - while (seqCanvas.cursorY + 1 > av.endSeq) + while (seqCanvas.cursorY + 1 > av.getRanges().getEndSeq()) { ap.scrollUp(false); } if (!av.getWrapAlignment()) { while (seqCanvas.cursorX < av.getColumnSelection() - .adjustForHiddenColumns(av.startRes)) + .adjustForHiddenColumns(av.getRanges().getStartRes())) { if (!ap.scrollRight(false)) { @@ -404,7 +406,7 @@ public class SeqPanel extends JPanel implements MouseListener, } } while (seqCanvas.cursorX > av.getColumnSelection() - .adjustForHiddenColumns(av.endRes)) + .adjustForHiddenColumns(av.getRanges().getEndRes())) { if (!ap.scrollRight(true)) { @@ -1587,34 +1589,15 @@ public class SeqPanel extends JPanel implements MouseListener, stretchGroup = av.getSelectionGroup(); - if (stretchGroup == null) + if (stretchGroup == null || !stretchGroup.contains(sequence, res)) { stretchGroup = av.getAlignment().findGroup(sequence, res); - av.setSelectionGroup(stretchGroup); - } - if (stretchGroup == null - || !stretchGroup.getSequences(null).contains(sequence) - || (stretchGroup.getStartRes() > res) - || (stretchGroup.getEndRes() < res)) - { - stretchGroup = null; - - SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence); - - if (allGroups != null) + if (stretchGroup != null) { - for (int i = 0; i < allGroups.length; i++) - { - if ((allGroups[i].getStartRes() <= res) - && (allGroups[i].getEndRes() >= res)) - { - stretchGroup = allGroups[i]; - break; - } - } + // only update the current selection if the popup menu has a group to + // focus on + av.setSelectionGroup(stretchGroup); } - - av.setSelectionGroup(stretchGroup); } if (evt.isPopupTrigger()) // Mac: mousePressed @@ -1664,6 +1647,7 @@ public class SeqPanel extends JPanel implements MouseListener, SliderPanel.setPIDSliderSource(ap, av.getResidueShading(), ap.getViewName()); } + // TODO: stretchGroup will always be not null. Is this a merge error ? if ((stretchGroup != null) && (stretchGroup.getEndRes() == res)) { // Edit end res position of selected group @@ -1790,9 +1774,9 @@ public class SeqPanel extends JPanel implements MouseListener, changeStartRes = true; } - if (res < av.getStartRes()) + if (res < av.getRanges().getStartRes()) { - res = av.getStartRes(); + res = av.getRanges().getStartRes(); } if (changeEndRes) @@ -1926,13 +1910,15 @@ public class SeqPanel extends JPanel implements MouseListener, { if (evt != null) { - if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0)) + if (mouseDragging && (evt.getY() < 0) + && (av.getRanges().getStartSeq() > 0)) { running = ap.scrollUp(true); } if (mouseDragging && (evt.getY() >= getHeight()) - && (av.getAlignment().getHeight() > av.getEndSeq())) + && (av.getAlignment().getHeight() > av.getRanges() + .getEndSeq())) { running = ap.scrollUp(false); } diff --git a/src/jalview/gui/SequenceRenderer.java b/src/jalview/gui/SequenceRenderer.java index 95c3261..1c0420d 100755 --- a/src/jalview/gui/SequenceRenderer.java +++ b/src/jalview/gui/SequenceRenderer.java @@ -20,10 +20,10 @@ */ package jalview.gui; -import jalview.api.FeatureRenderer; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.ResidueShaderI; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.util.Comparison; import java.awt.Color; @@ -53,14 +53,13 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer boolean forOverview = false; /** - * Creates a new SequenceRenderer object. + * Creates a new SequenceRenderer object * - * @param av - * DOCUMENT ME! + * @param viewport */ - public SequenceRenderer(AlignViewport av) + public SequenceRenderer(AlignViewport viewport) { - this.av = av; + this.av = viewport; } /** @@ -83,8 +82,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer this.renderGaps = renderGaps; } - @Override - public Color getResidueBoxColour(SequenceI seq, int i) + protected Color getResidueBoxColour(SequenceI seq, int i) { // rate limiting step when rendering overview for lots of groups allGroups = av.getAlignment().findAllGroups(seq); @@ -111,20 +109,18 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer * * @param seq * @param position - * @param fr + * @param finder * @return */ @Override public Color getResidueColour(final SequenceI seq, int position, - FeatureRenderer fr) + FeatureColourFinder finder) { - // TODO replace 8 or so code duplications with calls to this method - // (refactored as needed) Color col = getResidueBoxColour(seq, position); - if (fr != null) + if (finder != null) { - col = fr.findFeatureColour(col, seq, position); + col = finder.findFeatureColour(col, seq, position); } return col; } diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java index 34ad659..d7f7c31 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -110,6 +110,8 @@ public abstract class StructureViewerBase extends GStructureViewer protected boolean allChainsSelected = false; + protected JMenu viewSelectionMenu; + /** * Default constructor */ @@ -744,17 +746,17 @@ public abstract class StructureViewerBase extends GStructureViewer @Override public void itemStateChanged(ItemEvent e) { - alignStructs.setEnabled(_alignwith.size() > 0); + alignStructs.setEnabled(!_alignwith.isEmpty()); alignStructs.setToolTipText(MessageManager.formatMessage( "label.align_structures_using_linked_alignment_views", - new String[] { String.valueOf(_alignwith.size()) })); + _alignwith.size())); } }; - JMenu alpanels = new ViewSelectionMenu( + viewSelectionMenu = new ViewSelectionMenu( MessageManager.getString("label.superpose_with"), this, _alignwith, handler); handler.itemStateChanged(null); - viewerActionMenu.add(alpanels); + viewerActionMenu.add(viewSelectionMenu, 0); viewerActionMenu.addMenuListener(new MenuListener() { @Override @@ -781,16 +783,24 @@ public abstract class StructureViewerBase extends GStructureViewer public void setJalviewColourScheme(ColourSchemeI cs) { getBinding().setJalviewColourScheme(cs); } + + /** + * Sends commands to the structure viewer to superimpose structures based on + * currently associated alignments. May optionally return an error message for + * the operation. + */ @Override - protected void alignStructs_actionPerformed(ActionEvent actionEvent) + protected String alignStructs_actionPerformed( + ActionEvent actionEvent) { - alignStructs_withAllAlignPanels(); + return alignStructs_withAllAlignPanels(); } - protected void alignStructs_withAllAlignPanels() + + protected String alignStructs_withAllAlignPanels() { if (getAlignmentPanel() == null) { - return; + return null; } if (_alignwith.size() == 0) @@ -798,6 +808,7 @@ public abstract class StructureViewerBase extends GStructureViewer _alignwith.add(getAlignmentPanel()); } + String reply = null; try { AlignmentI[] als = new Alignment[_alignwith.size()]; @@ -811,7 +822,13 @@ public abstract class StructureViewerBase extends GStructureViewer alm[a] = -1; alc[a++] = ap.av.getColumnSelection(); } - getBinding().superposeStructures(als, alm, alc); + reply = getBinding().superposeStructures(als, alm, alc); + if (reply != null) + { + String text = MessageManager.formatMessage( + "error.superposition_failed", reply); + statusBar.setText(text); + } } catch (Exception e) { StringBuffer sp = new StringBuffer(); @@ -822,7 +839,9 @@ public abstract class StructureViewerBase extends GStructureViewer Cache.log.info("Couldn't align structures with the " + sp.toString() + "associated alignment panels.", e); } + return reply; } + @Override public void background_actionPerformed(ActionEvent actionEvent) { @@ -952,6 +971,10 @@ public abstract class StructureViewerBase extends GStructureViewer } protected abstract String getViewerName(); + + /** + * Configures the title and menu items of the viewer panel. + */ public void updateTitleAndMenus() { AAStructureBindingModel binding = getBinding(); @@ -963,10 +986,30 @@ public abstract class StructureViewerBase extends GStructureViewer setChainMenuItems(binding.getChainNames()); this.setTitle(binding.getViewerTitle(getViewerName(), true)); - if (binding.getPdbFile().length > 1 && binding.getSequence().length > 1) + + /* + * enable 'Superpose with' if more than one mapped structure + */ + viewSelectionMenu.setEnabled(false); + if (getBinding().getPdbFile().length > 1 + && getBinding().getSequence().length > 1) { - viewerActionMenu.setVisible(true); + viewSelectionMenu.setEnabled(true); } + + /* + * Show action menu if it has any enabled items + */ + viewerActionMenu.setVisible(false); + for (int i = 0; i < viewerActionMenu.getItemCount(); i++) + { + if (viewerActionMenu.getItem(i).isEnabled()) + { + viewerActionMenu.setVisible(true); + break; + } + } + if (!binding.isLoadingFromArchive()) { seqColour_actionPerformed(null); diff --git a/src/jalview/gui/TreeCanvas.java b/src/jalview/gui/TreeCanvas.java index 54eed1a..9a38d4c 100755 --- a/src/jalview/gui/TreeCanvas.java +++ b/src/jalview/gui/TreeCanvas.java @@ -549,7 +549,16 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, public void run() { PrinterJob printJob = PrinterJob.getPrinterJob(); - PageFormat pf = printJob.pageDialog(printJob.defaultPage()); + PageFormat defaultPage = printJob.defaultPage(); + PageFormat pf = printJob.pageDialog(defaultPage); + + if (defaultPage == pf) + { + /* + * user cancelled + */ + return; + } printJob.setPrintable(this, pf); diff --git a/src/jalview/gui/UserDefinedColours.java b/src/jalview/gui/UserDefinedColours.java index 83a8d24..463d8cd 100755 --- a/src/jalview/gui/UserDefinedColours.java +++ b/src/jalview/gui/UserDefinedColours.java @@ -29,6 +29,7 @@ import jalview.jbgui.GUserDefinedColours; import jalview.schemabinding.version2.Colour; import jalview.schemabinding.version2.JalviewUserColours; import jalview.schemes.ColourSchemeI; +import jalview.schemes.ColourSchemeLoader; import jalview.schemes.ColourSchemes; import jalview.schemes.ResidueProperties; import jalview.schemes.UserColourScheme; @@ -625,7 +626,7 @@ public class UserDefinedColours extends GUserDefinedColours implements File choice = chooser.getSelectedFile(); Cache.setProperty(LAST_DIRECTORY, choice.getParent()); - UserColourScheme ucs = ColourSchemes.loadColourScheme(choice + UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(choice .getAbsolutePath()); Color[] colors = ucs.getColours(); schemeName.setText(ucs.getSchemeName()); @@ -674,7 +675,7 @@ public class UserDefinedColours extends GUserDefinedColours implements { colours = colours.substring(0, colours.indexOf("|")); } - ret = ColourSchemes.loadColourScheme(colours); + ret = ColourSchemeLoader.loadColourScheme(colours); } if (ret == null) diff --git a/src/jalview/gui/WsPreferences.java b/src/jalview/gui/WsPreferences.java index 165e8f2..32671d5 100644 --- a/src/jalview/gui/WsPreferences.java +++ b/src/jalview/gui/WsPreferences.java @@ -454,7 +454,7 @@ public class WsPreferences extends GWsPreferences JTextField urltf = new JTextField(url, 40); JPanel panel = new JPanel(new BorderLayout()); JPanel pane12 = new JPanel(new BorderLayout()); - pane12.add(new JLabel(MessageManager.getString("label.url")), + pane12.add(new JLabel(MessageManager.getString("label.url:")), BorderLayout.CENTER); pane12.add(urltf, BorderLayout.EAST); panel.add(pane12, BorderLayout.NORTH); @@ -574,6 +574,7 @@ public class WsPreferences extends GWsPreferences new Thread(new Runnable() { + @Override public void run() { // force a refresh. @@ -599,6 +600,7 @@ public class WsPreferences extends GWsPreferences new Thread(new Runnable() { + @Override public void run() { progressBar.setVisible(true); @@ -624,6 +626,7 @@ public class WsPreferences extends GWsPreferences new Thread(new Runnable() { + @Override public void run() { long ct = System.currentTimeMillis(); @@ -681,6 +684,7 @@ public class WsPreferences extends GWsPreferences new Thread(new Runnable() { + @Override public void run() { updateWsMenuConfig(false); diff --git a/src/jalview/io/JSONFile.java b/src/jalview/io/JSONFile.java index 27ebe5a..053a65e 100644 --- a/src/jalview/io/JSONFile.java +++ b/src/jalview/io/JSONFile.java @@ -46,6 +46,7 @@ import jalview.json.binding.biojson.v1.ColourSchemeMapper; import jalview.json.binding.biojson.v1.SequenceFeaturesPojo; import jalview.json.binding.biojson.v1.SequenceGrpPojo; import jalview.json.binding.biojson.v1.SequencePojo; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.schemes.JalviewColourScheme; import jalview.schemes.ResidueColourScheme; import jalview.util.ColorUtils; @@ -328,6 +329,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile return sequenceFeaturesPojo; } + FeatureColourFinder finder = new FeatureColourFinder(fr); + for (SequenceI seq : sqs) { SequenceI dataSetSequence = seq.getDatasetSequence(); @@ -350,7 +353,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile String.valueOf(seq.hashCode())); String featureColour = (fr == null) ? null : jalview.util.Format - .getHexString(fr.findFeatureColour(Color.white, seq, + .getHexString(finder.findFeatureColour(Color.white, seq, seq.findIndex(sf.getBegin()))); jsonFeature.setXstart(seq.findIndex(sf.getBegin()) - 1); jsonFeature.setXend(seq.findIndex(sf.getEnd())); diff --git a/src/jalview/io/PDBFeatureSettings.java b/src/jalview/io/PDBFeatureSettings.java index 746c4a7..0f2b0ac 100644 --- a/src/jalview/io/PDBFeatureSettings.java +++ b/src/jalview/io/PDBFeatureSettings.java @@ -26,12 +26,14 @@ import jalview.schemes.FeatureSettingsAdapter; import java.awt.Color; +import MCview.PDBChain; + public class PDBFeatureSettings extends FeatureSettingsAdapter { // TODO find one central place to define feature names private static final String FEATURE_INSERTION = "INSERTION"; - private static final String FEATURE_RES_NUM = "RESNUM"; + private static final String FEATURE_RES_NUM = PDBChain.RESNUM_FEATURE; @Override public boolean isFeatureDisplayed(String type) diff --git a/src/jalview/javascript/MouseOverStructureListener.java b/src/jalview/javascript/MouseOverStructureListener.java index 4f833bc..7580222 100644 --- a/src/jalview/javascript/MouseOverStructureListener.java +++ b/src/jalview/javascript/MouseOverStructureListener.java @@ -221,8 +221,8 @@ public class MouseOverStructureListener extends JSFunctionExec implements ArrayList ccomands = new ArrayList(); ArrayList pdbfn = new ArrayList(); StructureMappingcommandSet[] colcommands = JmolCommands - .getColourBySequenceCommand(ssm, modelSet, sequence, sr, fr, - ((AlignmentViewPanel) source).getAlignment()); + .getColourBySequenceCommand(ssm, modelSet, sequence, sr, + (AlignmentViewPanel) source); if (colcommands == null) { return; @@ -298,6 +298,7 @@ public class MouseOverStructureListener extends JSFunctionExec implements return _listenerfn; } + @Override public void finalize() throws Throwable { jvlite = null; diff --git a/src/jalview/jbgui/GPreferences.java b/src/jalview/jbgui/GPreferences.java index 90053f5..dda06b4 100755 --- a/src/jalview/jbgui/GPreferences.java +++ b/src/jalview/jbgui/GPreferences.java @@ -45,6 +45,7 @@ import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import javax.swing.AbstractCellEditor; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.DefaultListCellRenderer; @@ -53,11 +54,11 @@ import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; -import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; +import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; @@ -67,8 +68,8 @@ import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; /** * Base class for the Preferences panel. @@ -180,7 +181,21 @@ public class GPreferences extends JPanel /* * Connections tab components */ - protected JList linkURLList = new JList(); + protected JTable linkUrlTable = new JTable(); + + protected JButton editLink = new JButton(); + + protected JButton deleteLink = new JButton(); + + protected JTextField filterTB = new JTextField(); + + protected JButton doReset = new JButton(); + + protected JButton userOnly = new JButton(); + + protected JLabel portLabel = new JLabel(); + + protected JLabel serverLabel = new JLabel(); protected JTextField proxyServerTB = new JTextField(); @@ -188,8 +203,6 @@ public class GPreferences extends JPanel protected JTextField defaultBrowser = new JTextField(); - protected JList linkNameList = new JList(); - protected JCheckBox useProxy = new JCheckBox(); protected JCheckBox usagestats = new JCheckBox(); @@ -285,6 +298,9 @@ public class GPreferences extends JPanel tabbedPane.add(initConnectionsTab(), MessageManager.getString("label.connections")); + tabbedPane.add(initLinksTab(), + MessageManager.getString("label.urllinks")); + tabbedPane.add(initOutputTab(), MessageManager.getString("label.output")); @@ -483,40 +499,196 @@ public class GPreferences extends JPanel { JPanel connectTab = new JPanel(); connectTab.setLayout(new GridBagLayout()); - JLabel serverLabel = new JLabel(); - serverLabel.setText(MessageManager.getString("label.address")); - serverLabel.setHorizontalAlignment(SwingConstants.RIGHT); - serverLabel.setFont(LABEL_FONT); - proxyServerTB.setFont(LABEL_FONT); - proxyPortTB.setFont(LABEL_FONT); - JLabel portLabel = new JLabel(); - portLabel.setFont(LABEL_FONT); - portLabel.setHorizontalAlignment(SwingConstants.RIGHT); - portLabel.setText(MessageManager.getString("label.port")); + + // Label for browser text box JLabel browserLabel = new JLabel(); - browserLabel.setFont(new java.awt.Font("SansSerif", 0, 11)); + browserLabel.setFont(LABEL_FONT); browserLabel.setHorizontalAlignment(SwingConstants.TRAILING); browserLabel.setText(MessageManager .getString("label.default_browser_unix")); defaultBrowser.setFont(LABEL_FONT); defaultBrowser.setText(""); - usagestats.setText(MessageManager - .getString("label.send_usage_statistics")); - usagestats.setFont(LABEL_FONT); - usagestats.setHorizontalAlignment(SwingConstants.RIGHT); - usagestats.setHorizontalTextPosition(SwingConstants.LEADING); - questionnaire.setText(MessageManager - .getString("label.check_for_questionnaires")); - questionnaire.setFont(LABEL_FONT); - questionnaire.setHorizontalAlignment(SwingConstants.RIGHT); - questionnaire.setHorizontalTextPosition(SwingConstants.LEADING); - versioncheck.setText(MessageManager - .getString("label.check_for_latest_version")); - versioncheck.setFont(LABEL_FONT); - versioncheck.setHorizontalAlignment(SwingConstants.RIGHT); - versioncheck.setHorizontalTextPosition(SwingConstants.LEADING); + + defaultBrowser.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + if (e.getClickCount() > 1) + { + defaultBrowser_mouseClicked(e); + } + } + }); + + JPanel proxyPanel = initConnTabProxyPanel(); + initConnTabCheckboxes(); + + // Add default Browser text box + connectTab.add(browserLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, + 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, + new Insets(10, 0, 5, 5), 5, 1)); + defaultBrowser.setFont(LABEL_FONT); + defaultBrowser.setText(""); + + connectTab.add(defaultBrowser, new GridBagConstraints(1, 0, 1, 1, 1.0, + 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(10, 0, 5, 10), 30, 1)); + + // Add proxy server panel + connectTab.add(proxyPanel, new GridBagConstraints(0, 1, 2, 1, 1.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(10, 0, 5, 12), 4, 10)); + + // Add usage stats, version check and questionnaire checkboxes + connectTab.add(usagestats, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(0, 2, 5, 5), 70, 1)); + connectTab.add(questionnaire, new GridBagConstraints(1, 2, 1, 1, 1.0, + 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(0, 2, 5, 10), 70, 1)); + connectTab.add(versioncheck, new GridBagConstraints(0, 3, 1, 1, 1.0, + 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(0, 2, 5, 5), 70, 1)); + + // Add padding so the panel doesn't look ridiculous + JPanel spacePanel = new JPanel(); + connectTab.add(spacePanel, new GridBagConstraints(0, 4, 1, 1, 1.0, 1.0, + GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, + 0, 0, 5), 70, 1)); + + return connectTab; + } + + /** + * Initialises the Links tabbed panel. + * + * @return + */ + private JPanel initLinksTab() + { + JPanel linkTab = new JPanel(); + linkTab.setLayout(new GridBagLayout()); + + // Set up table for Url links + linkUrlTable.setFillsViewportHeight(true); + linkUrlTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + linkUrlTable.setAutoCreateRowSorter(true); + linkUrlTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + // adjust row height so radio buttons actually fit + // don't do this in the renderer, it causes the awt thread to activate + // constantly + JRadioButton temp = new JRadioButton(); + linkUrlTable.setRowHeight(temp.getMinimumSize().height); + + // Table in scrollpane so that the table is given a scrollbar + JScrollPane linkScrollPane = new JScrollPane(linkUrlTable); + linkScrollPane.setBorder(null); + + // Panel for links functionality + JPanel linkPanel = new JPanel(new GridBagLayout()); + linkPanel.setBorder(new TitledBorder(MessageManager + .getString("label.url_linkfrom_sequence_id"))); + + // Put the Url links panel together + + // Buttons go at top right, resizing only resizes the blank space vertically + JPanel buttonPanel = initLinkTabUrlButtons(); + GridBagConstraints linkConstraints1 = new GridBagConstraints(); + linkConstraints1.insets = new Insets(0, 0, 5, 0); + linkConstraints1.gridx = 0; + linkConstraints1.gridy = 0; + linkConstraints1.weightx = 1.0; + linkConstraints1.fill = GridBagConstraints.HORIZONTAL; + linkTab.add(buttonPanel, linkConstraints1); + + // Links table goes at top left, resizing resizes the table + GridBagConstraints linkConstraints2 = new GridBagConstraints(); + linkConstraints2.insets = new Insets(0, 0, 5, 5); + linkConstraints2.gridx = 0; + linkConstraints2.gridy = 1; + linkConstraints2.weightx = 1.0; + linkConstraints2.weighty = 1.0; + linkConstraints2.fill = GridBagConstraints.BOTH; + linkTab.add(linkScrollPane, linkConstraints2); + + // Filter box and buttons goes at bottom left, resizing resizes the text box + JPanel filterPanel = initLinkTabFilterPanel(); + GridBagConstraints linkConstraints3 = new GridBagConstraints(); + linkConstraints3.insets = new Insets(0, 0, 0, 5); + linkConstraints3.gridx = 0; + linkConstraints3.gridy = 2; + linkConstraints3.weightx = 1.0; + linkConstraints3.fill = GridBagConstraints.HORIZONTAL; + linkTab.add(filterPanel, linkConstraints3); + + return linkTab; + } + + private JPanel initLinkTabFilterPanel() + { + // Filter textbox and reset button + JLabel filterLabel = new JLabel( + MessageManager.getString("label.filter")); + filterLabel.setFont(LABEL_FONT); + filterLabel.setHorizontalAlignment(SwingConstants.RIGHT); + filterLabel.setHorizontalTextPosition(SwingConstants.LEADING); + + filterTB.setFont(LABEL_FONT); + filterTB.setText(""); + + doReset.setText(MessageManager.getString("action.showall")); + userOnly.setText(MessageManager.getString("action.customfilter")); + + // Panel for filter functionality + JPanel filterPanel = new JPanel(new GridBagLayout()); + filterPanel.setBorder(new TitledBorder("Filter")); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.WEST; + + filterPanel.add(filterLabel, gbc); + + GridBagConstraints gbc1 = new GridBagConstraints(); + gbc1.gridx = 1; + gbc1.gridwidth = 2; + gbc1.fill = GridBagConstraints.HORIZONTAL; + gbc1.anchor = GridBagConstraints.WEST; + gbc1.weightx = 1.0; + filterPanel.add(filterTB, gbc1); + + GridBagConstraints gbc2 = new GridBagConstraints(); + gbc2.gridx = 3; + gbc2.fill = GridBagConstraints.NONE; + gbc2.anchor = GridBagConstraints.WEST; + filterPanel.add(doReset, gbc2); + + GridBagConstraints gbc3 = new GridBagConstraints(); + gbc3.gridx = 4; + gbc3.fill = GridBagConstraints.NONE; + gbc3.anchor = GridBagConstraints.WEST; + filterPanel.add(userOnly, gbc3); + + return filterPanel; + } + + private JPanel initLinkTabUrlButtons() + { + // Buttons for new / edit / delete Url links JButton newLink = new JButton(); newLink.setText(MessageManager.getString("action.new")); + + editLink.setText(MessageManager.getString("action.edit")); + + deleteLink.setText(MessageManager.getString("action.delete")); + + // no current selection, so initially disable delete/edit buttons + editLink.setEnabled(false); + deleteLink.setEnabled(false); + newLink.addActionListener(new java.awt.event.ActionListener() { @Override @@ -525,7 +697,7 @@ public class GPreferences extends JPanel newLink_actionPerformed(e); } }); - JButton editLink = new JButton(); + editLink.setText(MessageManager.getString("action.edit")); editLink.addActionListener(new java.awt.event.ActionListener() { @@ -535,7 +707,7 @@ public class GPreferences extends JPanel editLink_actionPerformed(e); } }); - JButton deleteLink = new JButton(); + deleteLink.setText(MessageManager.getString("action.delete")); deleteLink.addActionListener(new java.awt.event.ActionListener() { @@ -546,55 +718,60 @@ public class GPreferences extends JPanel } }); - linkURLList.addListSelectionListener(new ListSelectionListener() - { - @Override - public void valueChanged(ListSelectionEvent e) - { - int index = linkURLList.getSelectedIndex(); - linkNameList.setSelectedIndex(index); - } - }); + JPanel buttonPanel = new JPanel(new GridBagLayout()); + buttonPanel.setBorder(new TitledBorder("Edit links")); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.NONE; + buttonPanel.add(newLink, gbc); + + GridBagConstraints gbc1 = new GridBagConstraints(); + gbc1.gridx = 1; + gbc1.gridy = 0; + gbc1.fill = GridBagConstraints.NONE; + buttonPanel.add(editLink, gbc1); + + GridBagConstraints gbc2 = new GridBagConstraints(); + gbc2.gridx = 2; + gbc2.gridy = 0; + gbc2.fill = GridBagConstraints.NONE; + buttonPanel.add(deleteLink, gbc2); + + GridBagConstraints gbc3 = new GridBagConstraints(); + gbc3.gridx = 3; + gbc3.gridy = 0; + gbc3.fill = GridBagConstraints.HORIZONTAL; + gbc3.weightx = 1.0; + JPanel spacePanel = new JPanel(); + spacePanel.setBorder(null); + buttonPanel.add(spacePanel, gbc3); + + return buttonPanel; + } - linkNameList.addListSelectionListener(new ListSelectionListener() - { - @Override - public void valueChanged(ListSelectionEvent e) - { - int index = linkNameList.getSelectedIndex(); - linkURLList.setSelectedIndex(index); - } - }); + /** + * Initialises the proxy server panel in the Connections tab + * + * @return the proxy server panel + */ + private JPanel initConnTabProxyPanel() + { + // Label for server text box + serverLabel.setText(MessageManager.getString("label.address")); + serverLabel.setHorizontalAlignment(SwingConstants.RIGHT); + serverLabel.setFont(LABEL_FONT); - JScrollPane linkScrollPane = new JScrollPane(); - linkScrollPane.setBorder(null); - JPanel linkPanel = new JPanel(); - linkPanel.setBorder(new TitledBorder(MessageManager - .getString("label.url_linkfrom_sequence_id"))); - linkPanel.setLayout(new BorderLayout()); - GridLayout gridLayout1 = new GridLayout(); - JPanel editLinkButtons = new JPanel(); - editLinkButtons.setLayout(gridLayout1); - gridLayout1.setRows(3); - linkNameList.setFont(LABEL_FONT); - linkNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - BorderLayout borderLayout3 = new BorderLayout(); - JPanel linkPanel2 = new JPanel(); - linkPanel2.setLayout(borderLayout3); - linkURLList.setFont(LABEL_FONT); - linkURLList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + // Proxy server and port text boxes + proxyServerTB.setFont(LABEL_FONT); + proxyPortTB.setFont(LABEL_FONT); - defaultBrowser.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) - { - if (e.getClickCount() > 1) - { - defaultBrowser_mouseClicked(e); - } - } - }); + // Label for Port text box + portLabel.setFont(LABEL_FONT); + portLabel.setHorizontalAlignment(SwingConstants.RIGHT); + portLabel.setText(MessageManager.getString("label.port")); + + // Use proxy server checkbox useProxy.setFont(LABEL_FONT); useProxy.setHorizontalAlignment(SwingConstants.RIGHT); useProxy.setHorizontalTextPosition(SwingConstants.LEADING); @@ -607,56 +784,57 @@ public class GPreferences extends JPanel useProxy_actionPerformed(); } }); - linkPanel.add(editLinkButtons, BorderLayout.EAST); - editLinkButtons.add(newLink, null); - editLinkButtons.add(editLink, null); - editLinkButtons.add(deleteLink, null); - linkPanel.add(linkScrollPane, BorderLayout.CENTER); - linkScrollPane.getViewport().add(linkPanel2, null); - linkPanel2.add(linkURLList, BorderLayout.CENTER); - linkPanel2.add(linkNameList, BorderLayout.WEST); - JPanel jPanel1 = new JPanel(); + + // Make proxy server panel + JPanel proxyPanel = new JPanel(); TitledBorder titledBorder1 = new TitledBorder( MessageManager.getString("label.proxy_server")); - jPanel1.setBorder(titledBorder1); - jPanel1.setLayout(new GridBagLayout()); - jPanel1.add(serverLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, - 2, 4, 0), 5, 0)); - jPanel1.add(portLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, - 0, 4, 0), 11, 6)); - connectTab.add(linkPanel, new GridBagConstraints(0, 0, 2, 1, 1.0, 1.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( - 16, 0, 0, 12), 359, -17)); - connectTab.add(jPanel1, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( - 21, 0, 35, 12), 4, 6)); - connectTab.add(browserLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, + proxyPanel.setBorder(titledBorder1); + proxyPanel.setLayout(new GridBagLayout()); + proxyPanel.add(serverLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, - new Insets(16, 0, 0, 0), 5, 1)); - jPanel1.add(useProxy, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, + new Insets(0, 2, 2, 0), 5, 0)); + proxyPanel.add(portLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, + 0, 2, 0), 11, 0)); + proxyPanel.add(useProxy, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 5, 185), 2, -4)); - jPanel1.add(proxyPortTB, new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(0, 2, 4, 2), 54, 1)); - jPanel1.add(proxyServerTB, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(0, 2, 4, 0), 263, 1)); - connectTab.add(defaultBrowser, new GridBagConstraints(1, 1, 1, 1, 1.0, - 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(15, 0, 0, 15), 307, 1)); - connectTab.add(usagestats, new GridBagConstraints(0, 4, 1, 1, 1.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(0, 2, 4, 2), 70, 1)); - connectTab.add(questionnaire, new GridBagConstraints(1, 4, 1, 1, 1.0, + proxyPanel.add(proxyPortTB, new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(0, 2, 4, 2), 70, 1)); - connectTab.add(versioncheck, new GridBagConstraints(0, 5, 1, 1, 1.0, + new Insets(0, 2, 2, 2), 54, 1)); + proxyPanel.add(proxyServerTB, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(0, 2, 4, 2), 70, 1)); - return connectTab; + new Insets(0, 2, 2, 0), 263, 1)); + + return proxyPanel; + } + + /** + * Initialises the checkboxes in the Connections tab + */ + private void initConnTabCheckboxes() + { + // Usage stats checkbox label + usagestats.setText(MessageManager + .getString("label.send_usage_statistics")); + usagestats.setFont(LABEL_FONT); + usagestats.setHorizontalAlignment(SwingConstants.RIGHT); + usagestats.setHorizontalTextPosition(SwingConstants.LEADING); + + // Questionnaire checkbox label + questionnaire.setText(MessageManager + .getString("label.check_for_questionnaires")); + questionnaire.setFont(LABEL_FONT); + questionnaire.setHorizontalAlignment(SwingConstants.RIGHT); + questionnaire.setHorizontalTextPosition(SwingConstants.LEADING); + + // Check for latest version checkbox label + versioncheck.setText(MessageManager + .getString("label.check_for_latest_version")); + versioncheck.setFont(LABEL_FONT); + versioncheck.setHorizontalAlignment(SwingConstants.RIGHT); + versioncheck.setHorizontalTextPosition(SwingConstants.LEADING); } /** @@ -1357,8 +1535,82 @@ public class GPreferences extends JPanel public void useProxy_actionPerformed() { - proxyServerTB.setEnabled(useProxy.isSelected()); - proxyPortTB.setEnabled(useProxy.isSelected()); + boolean enabled = useProxy.isSelected(); + portLabel.setEnabled(enabled); + serverLabel.setEnabled(enabled); + proxyServerTB.setEnabled(enabled); + proxyPortTB.setEnabled(enabled); + } + + /** + * Customer renderer for JTable: supports column of radio buttons + */ + public class RadioButtonRenderer extends JRadioButton implements + TableCellRenderer + { + public RadioButtonRenderer() + { + setHorizontalAlignment(CENTER); + setToolTipText(MessageManager.getString("label.urltooltip")); + } + + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, boolean isSelected, boolean hasFocus, int row, + int column) + { + setSelected((boolean) value); + + // set colours to match rest of table + if (isSelected) + { + setBackground(table.getSelectionBackground()); + setForeground(table.getSelectionForeground()); + } + else + { + setBackground(table.getBackground()); + setForeground(table.getForeground()); + } + return this; + } } + /** + * Customer cell editor for JTable: supports column of radio buttons in + * conjunction with renderer + */ + public class RadioButtonEditor extends AbstractCellEditor implements + TableCellEditor + { + private JRadioButton button = new JRadioButton(); + + public RadioButtonEditor() + { + button.setHorizontalAlignment(SwingConstants.CENTER); + this.button.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + fireEditingStopped(); + } + }); + } + + @Override + public Component getTableCellEditorComponent(JTable table, + Object value, boolean isSelected, int row, int column) + { + button.setSelected((boolean) value); + return button; + } + + @Override + public Object getCellEditorValue() + { + return button.isSelected(); + } + + } } diff --git a/src/jalview/jbgui/GSequenceLink.java b/src/jalview/jbgui/GSequenceLink.java index dbce5f3..ab3ea2c 100755 --- a/src/jalview/jbgui/GSequenceLink.java +++ b/src/jalview/jbgui/GSequenceLink.java @@ -29,19 +29,48 @@ import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; -import java.awt.Panel; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.BorderFactory; +import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingConstants; -public class GSequenceLink extends Panel +public class GSequenceLink extends JPanel { + + JTextField nameTB = new JTextField(); + + JTextField urlTB = new JTextField(); + + JButton insertSeq = new JButton(); + + JButton insertDBAcc = new JButton(); + + JLabel insert = new JLabel(); + + JLabel jLabel1 = new JLabel(); + + JLabel jLabel2 = new JLabel(); + + JLabel jLabel3 = new JLabel(); + + JLabel jLabel4 = new JLabel(); + + JLabel jLabel5 = new JLabel(); + + JLabel jLabel6 = new JLabel(); + + JPanel jPanel1 = new JPanel(); + + GridBagLayout gridBagLayout1 = new GridBagLayout(); + public GSequenceLink() { try @@ -77,23 +106,53 @@ public class GSequenceLink extends Panel urlTB_keyTyped(e); } }); + + insertSeq.setLocation(77, 75); + insertSeq.setSize(141, 24); + insertSeq.setText(MessageManager.getString("action.seq_id")); + insertSeq.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + insertSeq_action(e); + } + }); + + insertDBAcc.setLocation(210, 75); + insertDBAcc.setSize(141, 24); + insertDBAcc.setText(MessageManager.getString("action.db_acc")); + insertDBAcc.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + insertDBAcc_action(e); + } + }); + + insert.setText(MessageManager.getString("label.insert")); + insert.setFont(JvSwingUtils.getLabelFont()); + insert.setHorizontalAlignment(SwingConstants.RIGHT); + insert.setBounds(17, 78, 58, 16); + jLabel1.setFont(JvSwingUtils.getLabelFont()); jLabel1.setHorizontalAlignment(SwingConstants.TRAILING); jLabel1.setText(MessageManager.getString("label.link_name")); jLabel1.setBounds(new Rectangle(4, 10, 71, 24)); jLabel2.setFont(JvSwingUtils.getLabelFont()); jLabel2.setHorizontalAlignment(SwingConstants.TRAILING); - jLabel2.setText(MessageManager.getString("label.url")); + jLabel2.setText(MessageManager.getString("label.url:")); jLabel2.setBounds(new Rectangle(17, 37, 54, 27)); jLabel3.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11)); jLabel3.setText(MessageManager.getString("label.use_sequence_id_1")); - jLabel3.setBounds(new Rectangle(21, 72, 351, 15)); + jLabel3.setBounds(new Rectangle(21, 102, 351, 15)); jLabel4.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11)); jLabel4.setText(MessageManager.getString("label.use_sequence_id_2")); - jLabel4.setBounds(new Rectangle(21, 88, 351, 15)); + jLabel4.setBounds(new Rectangle(21, 118, 351, 15)); jLabel5.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11)); jLabel5.setText(MessageManager.getString("label.use_sequence_id_3")); - jLabel5.setBounds(new Rectangle(21, 106, 351, 15)); + jLabel5.setBounds(new Rectangle(21, 136, 351, 15)); String lastLabel = MessageManager.getString("label.use_sequence_id_4"); if (lastLabel.length() > 0) @@ -101,7 +160,7 @@ public class GSequenceLink extends Panel // e.g. Spanish version has longer text jLabel6.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11)); jLabel6.setText(lastLabel); - jLabel6.setBounds(new Rectangle(21, 122, 351, 15)); + jLabel6.setBounds(new Rectangle(21, 152, 351, 15)); } jPanel1.setBorder(BorderFactory.createEtchedBorder()); @@ -109,16 +168,19 @@ public class GSequenceLink extends Panel jPanel1.add(jLabel1); jPanel1.add(nameTB); jPanel1.add(urlTB); + jPanel1.add(insertSeq); + jPanel1.add(insertDBAcc); + jPanel1.add(insert); jPanel1.add(jLabel2); jPanel1.add(jLabel3); jPanel1.add(jLabel4); jPanel1.add(jLabel5); - int height = 130; + int height = 160; if (lastLabel.length() > 0) { jPanel1.add(jLabel6); - height = 146; + height = 176; } this.add(jPanel1, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, @@ -163,25 +225,13 @@ public class GSequenceLink extends Panel return false; } - JTextField nameTB = new JTextField(); - - JTextField urlTB = new JTextField(); - - JLabel jLabel1 = new JLabel(); - - JLabel jLabel2 = new JLabel(); - - JLabel jLabel3 = new JLabel(); - - JLabel jLabel4 = new JLabel(); - - JLabel jLabel5 = new JLabel(); - - JLabel jLabel6 = new JLabel(); - - JPanel jPanel1 = new JPanel(); - - GridBagLayout gridBagLayout1 = new GridBagLayout(); + public void notifyDuplicate() + { + JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop, + MessageManager.getString("warn.name_cannot_be_duplicate"), + MessageManager.getString("label.invalid_name"), + JvOptionPane.WARNING_MESSAGE); + } public void nameTB_keyTyped(KeyEvent e) { @@ -200,4 +250,23 @@ public class GSequenceLink extends Panel // } } + + public void insertSeq_action(ActionEvent e) + { + insertIntoUrl(insertSeq.getText()); + } + + public void insertDBAcc_action(ActionEvent e) + { + insertIntoUrl(insertDBAcc.getText()); + } + + private void insertIntoUrl(String insertion) + { + int pos = urlTB.getCaretPosition(); + String text = urlTB.getText(); + String newText = text.substring(0, pos) + insertion + + text.substring(pos); + urlTB.setText(newText); + } } diff --git a/src/jalview/jbgui/GStructureViewer.java b/src/jalview/jbgui/GStructureViewer.java index 6b89ab4..d8f3f61 100644 --- a/src/jalview/jbgui/GStructureViewer.java +++ b/src/jalview/jbgui/GStructureViewer.java @@ -170,8 +170,8 @@ public abstract class GStructureViewer extends JInternalFrame implements } }); alignStructs = new JMenuItem(); - alignStructs - .setText(MessageManager.getString("label.align_structures")); + alignStructs.setText(MessageManager + .getString("label.superpose_structures")); alignStructs.addActionListener(new ActionListener() { @Override @@ -181,7 +181,7 @@ public abstract class GStructureViewer extends JInternalFrame implements } }); - viewerActionMenu = new JMenu(); + viewerActionMenu = new JMenu(); // text set in sub-classes viewerActionMenu.setVisible(false); viewerActionMenu.add(alignStructs); colourMenu = new JMenu(); @@ -219,9 +219,8 @@ public abstract class GStructureViewer extends JInternalFrame implements { } - protected void alignStructs_actionPerformed(ActionEvent actionEvent) - { - } + protected abstract String alignStructs_actionPerformed( + ActionEvent actionEvent); public void pdbFile_actionPerformed(ActionEvent actionEvent) { diff --git a/src/jalview/math/Matrix.java b/src/jalview/math/Matrix.java index 647fc3a..de0bf77 100755 --- a/src/jalview/math/Matrix.java +++ b/src/jalview/math/Matrix.java @@ -28,36 +28,43 @@ import java.io.PrintStream; /** * A class to model rectangular matrices of double values and operations on them */ -public class Matrix +public class Matrix implements MatrixI { /* * the cell values in row-major order */ - public double[][] value; + private double[][] value; /* * the number of rows */ - public int rows; + protected int rows; /* * the number of columns */ - public int cols; + protected int cols; - /** DOCUMENT ME!! */ - public double[] d; // Diagonal + protected double[] d; // Diagonal - /** DOCUMENT ME!! */ - public double[] e; // off diagonal + protected double[] e; // off diagonal /** * maximum number of iterations for tqli * */ - int maxIter = 45; // fudge - add 15 iterations, just in case + private static final int maxIter = 45; // fudge - add 15 iterations, just in + // case /** + * Default constructor + */ + public Matrix() + { + + } + + /** * Creates a new Matrix object. For example * *

    @@ -82,11 +89,12 @@ public class Matrix
       }
     
       /**
    -   * Returns a new matrix which is the transposes of this one
    +   * Returns a new matrix which is the transpose of this one
        * 
        * @return DOCUMENT ME!
        */
    -  public Matrix transpose()
    +  @Override
    +  public MatrixI transpose()
       {
         double[][] out = new double[cols][rows];
     
    @@ -106,14 +114,16 @@ public class Matrix
        * 
        * @param ps
        *          DOCUMENT ME!
    +   * @param format
        */
    -  public void print(PrintStream ps)
    +  @Override
    +  public void print(PrintStream ps, String format)
       {
         for (int i = 0; i < rows; i++)
         {
           for (int j = 0; j < cols; j++)
           {
    -        Format.print(ps, "%8.2f", value[i][j]);
    +        Format.print(ps, format, getValue(i, j));
           }
     
           ps.println();
    @@ -132,24 +142,27 @@ public class Matrix
        *           if the number of columns in the pre-multiplier is not equal to
        *           the number of rows in the multiplicand (this)
        */
    -  public Matrix preMultiply(Matrix in)
    +  @Override
    +  public MatrixI preMultiply(MatrixI in)
       {
    -    if (in.cols != this.rows)
    +    if (in.width() != rows)
         {
           throw new IllegalArgumentException("Can't pre-multiply " + this.rows
    -              + " rows by " + in.cols + " columns");
    +              + " rows by " + in.width() + " columns");
         }
    -    double[][] tmp = new double[in.rows][this.cols];
    +    double[][] tmp = new double[in.height()][this.cols];
     
    -    for (int i = 0; i < in.rows; i++)
    +    for (int i = 0; i < in.height(); i++)
         {
           for (int j = 0; j < this.cols; j++)
           {
    -        tmp[i][j] = 0.0;
    -
    -        for (int k = 0; k < in.cols; k++)
    +        /*
    +         * result[i][j] is the vector product of 
    +         * in.row[i] and this.column[j]
    +         */
    +        for (int k = 0; k < in.width(); k++)
             {
    -          tmp[i][j] += (in.value[i][k] * this.value[k][j]);
    +          tmp[i][j] += (in.getValue(i, k) * this.value[k][j]);
             }
           }
         }
    @@ -195,12 +208,13 @@ public class Matrix
        *           number of columns in the multiplicand (this)
        * @see #preMultiply(Matrix)
        */
    -  public Matrix postMultiply(Matrix in)
    +  @Override
    +  public MatrixI postMultiply(MatrixI in)
       {
    -    if (in.rows != this.cols)
    +    if (in.height() != this.cols)
         {
           throw new IllegalArgumentException("Can't post-multiply " + this.cols
    -              + " columns by " + in.rows + " rows");
    +              + " columns by " + in.height() + " rows");
         }
         return in.preMultiply(this);
       }
    @@ -210,7 +224,8 @@ public class Matrix
        * 
        * @return
        */
    -  public Matrix copy()
    +  @Override
    +  public MatrixI copy()
       {
         double[][] newmat = new double[rows][cols];
     
    @@ -225,10 +240,10 @@ public class Matrix
       /**
        * DOCUMENT ME!
        */
    +  @Override
       public void tred()
       {
         int n = rows;
    -    int l;
         int k;
         int j;
         int i;
    @@ -244,7 +259,7 @@ public class Matrix
     
         for (i = n; i >= 2; i--)
         {
    -      l = i - 1;
    +      final int l = i - 1;
           h = 0.0;
           scale = 0.0;
     
    @@ -252,22 +267,23 @@ public class Matrix
           {
             for (k = 1; k <= l; k++)
             {
    -          scale += Math.abs(value[i - 1][k - 1]);
    +          double v = Math.abs(getValue(i - 1, k - 1));
    +          scale += v;
             }
     
             if (scale == 0.0)
             {
    -          e[i - 1] = value[i - 1][l - 1];
    +          e[i - 1] = getValue(i - 1, l - 1);
             }
             else
             {
               for (k = 1; k <= l; k++)
               {
    -            value[i - 1][k - 1] /= scale;
    -            h += (value[i - 1][k - 1] * value[i - 1][k - 1]);
    +            double v = divideValue(i - 1, k - 1, scale);
    +            h += v * v;
               }
     
    -          f = value[i - 1][l - 1];
    +          f = getValue(i - 1, l - 1);
     
               if (f > 0)
               {
    @@ -280,46 +296,48 @@ public class Matrix
     
               e[i - 1] = scale * g;
               h -= (f * g);
    -          value[i - 1][l - 1] = f - g;
    +          setValue(i - 1, l - 1, f - g);
               f = 0.0;
     
               for (j = 1; j <= l; j++)
               {
    -            value[j - 1][i - 1] = value[i - 1][j - 1] / h;
    +            double val = getValue(i - 1, j - 1) / h;
    +            setValue(j - 1, i - 1, val);
                 g = 0.0;
     
                 for (k = 1; k <= j; k++)
                 {
    -              g += (value[j - 1][k - 1] * value[i - 1][k - 1]);
    +              g += (getValue(j - 1, k - 1) * getValue(i - 1, k - 1));
                 }
     
                 for (k = j + 1; k <= l; k++)
                 {
    -              g += (value[k - 1][j - 1] * value[i - 1][k - 1]);
    +              g += (getValue(k - 1, j - 1) * getValue(i - 1, k - 1));
                 }
     
                 e[j - 1] = g / h;
    -            f += (e[j - 1] * value[i - 1][j - 1]);
    +            f += (e[j - 1] * getValue(i - 1, j - 1));
               }
     
               hh = f / (h + h);
     
               for (j = 1; j <= l; j++)
               {
    -            f = value[i - 1][j - 1];
    +            f = getValue(i - 1, j - 1);
                 g = e[j - 1] - (hh * f);
                 e[j - 1] = g;
     
                 for (k = 1; k <= j; k++)
                 {
    -              value[j - 1][k - 1] -= ((f * e[k - 1]) + (g * value[i - 1][k - 1]));
    +              double val = (f * e[k - 1]) + (g * getValue(i - 1, k - 1));
    +              addValue(j - 1, k - 1, -val);
                 }
               }
             }
           }
           else
           {
    -        e[i - 1] = value[i - 1][l - 1];
    +        e[i - 1] = getValue(i - 1, l - 1);
           }
     
           d[i - 1] = h;
    @@ -330,7 +348,7 @@ public class Matrix
     
         for (i = 1; i <= n; i++)
         {
    -      l = i - 1;
    +      final int l = i - 1;
     
           if (d[i - 1] != 0.0)
           {
    @@ -340,30 +358,66 @@ public class Matrix
     
               for (k = 1; k <= l; k++)
               {
    -            g += (value[i - 1][k - 1] * value[k - 1][j - 1]);
    +            g += (getValue(i - 1, k - 1) * getValue(k - 1, j - 1));
               }
     
               for (k = 1; k <= l; k++)
               {
    -            value[k - 1][j - 1] -= (g * value[k - 1][i - 1]);
    +            addValue(k - 1, j - 1, -(g * getValue(k - 1, i - 1)));
               }
             }
           }
     
    -      d[i - 1] = value[i - 1][i - 1];
    -      value[i - 1][i - 1] = 1.0;
    +      d[i - 1] = getValue(i - 1, i - 1);
    +      setValue(i - 1, i - 1, 1.0);
     
           for (j = 1; j <= l; j++)
           {
    -        value[j - 1][i - 1] = 0.0;
    -        value[i - 1][j - 1] = 0.0;
    +        setValue(j - 1, i - 1, 0.0);
    +        setValue(i - 1, j - 1, 0.0);
           }
         }
       }
     
       /**
    +   * Adds f to the value at [i, j] and returns the new value
    +   * 
    +   * @param i
    +   * @param j
    +   * @param f
    +   */
    +  protected double addValue(int i, int j, double f)
    +  {
    +    double v = value[i][j] + f;
    +    value[i][j] = v;
    +    return v;
    +  }
    +
    +  /**
    +   * Divides the value at [i, j] by divisor and returns the new value. If d is
    +   * zero, returns the unchanged value.
    +   * 
    +   * @param i
    +   * @param j
    +   * @param divisor
    +   * @return
    +   */
    +  protected double divideValue(int i, int j, double divisor)
    +  {
    +    if (divisor == 0d)
    +    {
    +      return getValue(i, j);
    +    }
    +    double v = value[i][j];
    +    v = v / divisor;
    +    value[i][j] = v;
    +    return v;
    +  }
    +
    +  /**
        * DOCUMENT ME!
        */
    +  @Override
       public void tqli() throws Exception
       {
         int n = rows;
    @@ -376,7 +430,6 @@ public class Matrix
         double s;
         double r;
         double p;
    -    ;
     
         double g;
         double f;
    @@ -459,9 +512,9 @@ public class Matrix
     
                 for (k = 1; k <= n; k++)
                 {
    -              f = value[k - 1][i];
    -              value[k - 1][i] = (s * value[k - 1][i - 1]) + (c * f);
    -              value[k - 1][i - 1] = (c * value[k - 1][i - 1]) - (s * f);
    +              f = getValue(k - 1, i);
    +              setValue(k - 1, i, (s * getValue(k - 1, i - 1)) + (c * f));
    +              setValue(k - 1, i - 1, (c * getValue(k - 1, i - 1)) - (s * f));
                 }
               }
     
    @@ -473,6 +526,17 @@ public class Matrix
         }
       }
     
    +  @Override
    +  public double getValue(int i, int j)
    +  {
    +    return value[i][j];
    +  }
    +
    +  public void setValue(int i, int j, double val)
    +  {
    +    value[i][j] = val;
    +  }
    +
       /**
        * DOCUMENT ME!
        */
    @@ -725,16 +789,14 @@ public class Matrix
       }
     
       /**
    -   * DOCUMENT ME!
    +   * Answers the first argument with the sign of the second argument
        * 
        * @param a
    -   *          DOCUMENT ME!
        * @param b
    -   *          DOCUMENT ME!
        * 
    -   * @return DOCUMENT ME!
    +   * @return
        */
    -  public double sign(double a, double b)
    +  static double sign(double a, double b)
       {
         if (b < 0)
         {
    @@ -770,12 +832,14 @@ public class Matrix
        * 
        * @param ps
        *          DOCUMENT ME!
    +   * @param format
        */
    -  public void printD(PrintStream ps)
    +  @Override
    +  public void printD(PrintStream ps, String format)
       {
         for (int j = 0; j < rows; j++)
         {
    -      Format.print(ps, "%15.4e", d[j]);
    +      Format.print(ps, format, d[j]);
         }
       }
     
    @@ -784,12 +848,45 @@ public class Matrix
        * 
        * @param ps
        *          DOCUMENT ME!
    +   * @param format TODO
        */
    -  public void printE(PrintStream ps)
    +  @Override
    +  public void printE(PrintStream ps, String format)
       {
         for (int j = 0; j < rows; j++)
         {
    -      Format.print(ps, "%15.4e", e[j]);
    +      Format.print(ps, format, e[j]);
         }
       }
    +
    +  @Override
    +  public double[] getD()
    +  {
    +    return d;
    +  }
    +
    +  @Override
    +  public double[] getE()
    +  {
    +    return e;
    +  }
    +  
    +  @Override
    +  public int height() {
    +    return rows;
    +  }
    +
    +  @Override
    +  public int width()
    +  {
    +    return cols;
    +  }
    +
    +  @Override
    +  public double[] getRow(int i)
    +  {
    +    double[] row = new double[cols];
    +    System.arraycopy(value[i], 0, row, 0, cols);
    +    return row;
    +  }
     }
    diff --git a/src/jalview/math/MatrixI.java b/src/jalview/math/MatrixI.java
    new file mode 100644
    index 0000000..d74a98b
    --- /dev/null
    +++ b/src/jalview/math/MatrixI.java
    @@ -0,0 +1,59 @@
    +package jalview.math;
    +
    +import java.io.PrintStream;
    +
    +public interface MatrixI
    +{
    +  /**
    +   * Answers the number of columns
    +   * 
    +   * @return
    +   */
    +  int width();
    +
    +  /**
    +   * Answers the number of rows
    +   * 
    +   * @return
    +   */
    +  int height();
    +
    +  /**
    +   * Answers the value at row i, column j
    +   * 
    +   * @param i
    +   * @param j
    +   * @return
    +   */
    +  double getValue(int i, int j);
    +
    +  /**
    +   * Answers a copy of the values in the i'th row
    +   * 
    +   * @return
    +   */
    +  double[] getRow(int i);
    +  
    +  MatrixI copy();
    +
    +  MatrixI transpose();
    +
    +  MatrixI preMultiply(MatrixI m);
    +
    +  MatrixI postMultiply(MatrixI m);
    +
    +  double[] getD();
    +
    +  double[] getE();
    +
    +  void print(PrintStream ps, String format);
    +
    +  void printD(PrintStream ps, String format);
    +
    +  void printE(PrintStream ps, String format);
    +
    +  void tqli() throws Exception;
    +
    +  void tred();
    +
    +}
    diff --git a/src/jalview/math/SparseMatrix.java b/src/jalview/math/SparseMatrix.java
    new file mode 100644
    index 0000000..72f0963
    --- /dev/null
    +++ b/src/jalview/math/SparseMatrix.java
    @@ -0,0 +1,218 @@
    +package jalview.math;
    +
    +import jalview.ext.android.SparseDoubleArray;
    +
    +/**
    + * A variant of Matrix intended for use for sparse (mostly zero) matrices. This
    + * class uses a SparseDoubleArray to hold each row of the matrix. The sparse
    + * array only stores non-zero values. This gives a smaller memory footprint, and
    + * fewer matrix calculation operations, for mostly zero matrices.
    + * 
    + * @author gmcarstairs
    + */
    +public class SparseMatrix extends Matrix
    +{
    +  /*
    +   * we choose columns for the sparse arrays as this allows
    +   * optimisation of the preMultiply() method used in PCA.run()
    +   */
    +  SparseDoubleArray[] sparseColumns;
    +
    +  /**
    +   * Constructor given data in [row][column] order
    +   * 
    +   * @param v
    +   */
    +  public SparseMatrix(double[][] v)
    +  {
    +    rows = v.length;
    +    if (rows > 0) {
    +      cols = v[0].length;
    +    }
    +    sparseColumns = new SparseDoubleArray[cols];
    +
    +    /*
    +     * transpose v[row][col] into [col][row] order
    +     */
    +    for (int col = 0; col < cols; col++)
    +    {
    +      SparseDoubleArray sparseColumn = new SparseDoubleArray();
    +      sparseColumns[col] = sparseColumn;
    +      for (int row = 0; row < rows; row++)
    +      {
    +        double value = v[row][col];
    +        if (value != 0d)
    +        {
    +          sparseColumn.put(row, value);
    +        }
    +      }
    +    }
    +  }
    +
    +  /**
    +   * Answers the value at row i, column j
    +   */
    +  @Override
    +  public double getValue(int i, int j)
    +  {
    +    return sparseColumns[j].get(i);
    +  }
    +
    +  /**
    +   * Sets the value at row i, column j to val
    +   */
    +  @Override
    +  public void setValue(int i, int j, double val)
    +  {
    +    if (val == 0d)
    +    {
    +      sparseColumns[j].delete(i);
    +    }
    +    else
    +    {
    +      sparseColumns[j].put(i, val);
    +    }
    +  }
    +
    +  @Override
    +  public double[] getColumn(int i)
    +  {
    +    double[] col = new double[height()];
    +
    +    SparseDoubleArray vals = sparseColumns[i];
    +    for (int nonZero = 0; nonZero < vals.size(); nonZero++)
    +    {
    +      col[vals.keyAt(nonZero)] = vals.valueAt(nonZero);
    +    }
    +    return col;
    +  }
    +
    +  @Override
    +  public MatrixI copy()
    +  {
    +    double[][] vals = new double[height()][width()];
    +    for (int i = 0; i < height(); i++)
    +    {
    +      vals[i] = getRow(i);
    +    }
    +    return new SparseMatrix(vals);
    +  }
    +
    +  @Override
    +  public MatrixI transpose()
    +  {
    +    double[][] out = new double[cols][rows];
    +
    +    /*
    +     * for each column...
    +     */
    +    for (int i = 0; i < cols; i++)
    +    {
    +      /*
    +       * put non-zero values into the corresponding row
    +       * of the transposed matrix
    +       */
    +      SparseDoubleArray vals = sparseColumns[i];
    +      for (int nonZero = 0; nonZero < vals.size(); nonZero++)
    +      {
    +        out[i][vals.keyAt(nonZero)] = vals.valueAt(nonZero);
    +      }
    +    }
    +
    +    return new SparseMatrix(out);
    +  }
    +
    +  /**
    +   * Answers a new matrix which is the product in.this. If the product contains
    +   * less than 20% non-zero values, it is returned as a SparseMatrix, else as a
    +   * Matrix.
    +   * 

    + * This method is optimised for the sparse arrays which store column values + * for a SparseMatrix. Note that postMultiply is not so optimised. That would + * require redundantly also storing sparse arrays for the rows, which has not + * been done. Currently only preMultiply is used in Jalview. + */ + @Override + public MatrixI preMultiply(MatrixI in) + { + if (in.width() != rows) + { + throw new IllegalArgumentException("Can't pre-multiply " + this.rows + + " rows by " + in.width() + " columns"); + } + double[][] tmp = new double[in.height()][this.cols]; + + long count = 0L; + for (int i = 0; i < in.height(); i++) + { + for (int j = 0; j < this.cols; j++) + { + /* + * result[i][j] is the vector product of + * in.row[i] and this.column[j] + * we only need to use non-zero values from the column + */ + SparseDoubleArray vals = sparseColumns[j]; + boolean added = false; + for (int nonZero = 0; nonZero < vals.size(); nonZero++) + { + int myRow = vals.keyAt(nonZero); + double myValue = vals.valueAt(nonZero); + tmp[i][j] += (in.getValue(i, myRow) * myValue); + added = true; + } + if (added && tmp[i][j] != 0d) + { + count++; // non-zero entry in product + } + } + } + + /* + * heuristic rule - if product is more than 80% zero + * then construct a SparseMatrix, else a Matrix + */ + if (count * 5 < in.height() * cols) + { + return new SparseMatrix(tmp); + } + else + { + return new Matrix(tmp); + } + } + + @Override + protected double divideValue(int i, int j, double divisor) + { + if (divisor == 0d) + { + return getValue(i, j); + } + double v = sparseColumns[j].divide(i, divisor); + return v; + } + + @Override + protected double addValue(int i, int j, double addend) + { + double v = sparseColumns[j].add(i, addend); + return v; + } + + /** + * Returns the fraction of the whole matrix size that is actually modelled in + * sparse arrays (normally, the non-zero values) + * + * @return + */ + public float getFillRatio() + { + long count = 0L; + for (SparseDoubleArray col : sparseColumns) + { + count += col.size(); + } + return count / (float) (height() * width()); + } +} diff --git a/src/jalview/renderer/AnnotationRenderer.java b/src/jalview/renderer/AnnotationRenderer.java index 6f84a2e..3a27c7d 100644 --- a/src/jalview/renderer/AnnotationRenderer.java +++ b/src/jalview/renderer/AnnotationRenderer.java @@ -307,7 +307,7 @@ public class AnnotationRenderer public void updateFromAlignViewport(AlignViewportI av) { charWidth = av.getCharWidth(); - endRes = av.getEndRes(); + endRes = av.getRanges().getEndRes(); charHeight = av.getCharHeight(); hasHiddenColumns = av.hasHiddenColumns(); validCharWidth = av.isValidCharWidth(); diff --git a/src/jalview/renderer/ResidueShader.java b/src/jalview/renderer/ResidueShader.java index 3f5cd11..b6f7fe6 100644 --- a/src/jalview/renderer/ResidueShader.java +++ b/src/jalview/renderer/ResidueShader.java @@ -47,7 +47,7 @@ public class ResidueShader implements ResidueShaderI private boolean conservationColouring; /* - * the phsyico-chemical property conservation scores for columns, with values + * the physico-chemical property conservation scores for columns, with values * 0-9, '+' (all properties conserved), '*' (residue fully conserved) or '-' (gap) * (may be null if colour by conservation is not selected) */ diff --git a/src/jalview/renderer/seqfeatures/FeatureColourFinder.java b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java new file mode 100644 index 0000000..1db2004 --- /dev/null +++ b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java @@ -0,0 +1,124 @@ +package jalview.renderer.seqfeatures; + +import jalview.api.FeatureRenderer; +import jalview.api.FeaturesDisplayedI; +import jalview.datamodel.SequenceI; +import jalview.viewmodel.seqfeatures.FeatureRendererModel; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; + +/** + * A helper class to find feature colour using an associated FeatureRenderer + * + * @author gmcarstairs + * + */ +public class FeatureColourFinder +{ + /* + * the class we delegate feature finding to + */ + private FeatureRenderer featureRenderer; + + /* + * a 1-pixel image on which features can be drawn, for the case where + * transparency allows 'see-through' of multiple feature colours + */ + private BufferedImage offscreenImage; + + /** + * Constructor + * + * @param fr + */ + public FeatureColourFinder(FeatureRenderer fr) + { + featureRenderer = fr; + offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } + + /** + * Answers the feature colour to show for the given sequence and column + * position. This delegates to the FeatureRenderer to find the colour, which + * will depend on feature location, visibility, ordering, colour scheme, and + * whether or not transparency is applied. For feature rendering with + * transparency, this class provides a dummy 'offscreen' graphics context + * where multiple feature colours can be overlaid and the combined colour read + * back. + *

    + * This method is not thread-safe when transparency is applied, since a shared + * BufferedImage would be used by all threads to hold the composite colour at + * a position. Each thread should use a separate instance of this class. + * + * @param defaultColour + * @param seq + * @param column + * alignment column position (base zero) + * @return + */ + public Color findFeatureColour(Color defaultColour, SequenceI seq, + int column) + { + if (noFeaturesDisplayed()) + { + return defaultColour; + } + + Graphics g = null; + + /* + * if transparency applies, provide a notional 1x1 graphics context + * that has been primed with the default colour + */ + if (featureRenderer.getTransparency() != 1f) + { + g = offscreenImage.getGraphics(); + if (defaultColour != null) + { + offscreenImage.setRGB(0, 0, defaultColour.getRGB()); + } + } + + Color c = featureRenderer.findFeatureColour(seq, column, g); + if (c == null) + { + return defaultColour; + } + + if (g != null) + { + c = new Color(offscreenImage.getRGB(0, 0)); + } + return c; + } + + /** + * Answers true if feature display is turned off, or there are no features + * configured to be visible + * + * @return + */ + boolean noFeaturesDisplayed() + { + if (featureRenderer == null + || !featureRenderer.getViewport().isShowSequenceFeatures()) + { + return true; + } + + if (!((FeatureRendererModel) featureRenderer).hasRenderOrder()) + { + return true; + } + + FeaturesDisplayedI displayed = featureRenderer.getFeaturesDisplayed(); + if (displayed == null || displayed.getVisibleFeatureCount() == 0) + { + return true; + } + + return false; + } +} diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index 9e0089f..72ac2c8 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -23,6 +23,7 @@ package jalview.renderer.seqfeatures; import jalview.api.AlignViewportI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.util.Comparison; import jalview.viewmodel.seqfeatures.FeatureRendererModel; import java.awt.AlphaComposite; @@ -30,28 +31,11 @@ import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.image.BufferedImage; public class FeatureRenderer extends FeatureRendererModel { - - FontMetrics fm; - - int charOffset; - - boolean offscreenRender = false; - - protected SequenceI lastSeq; - - char s; - - int i; - - int av_charHeight, av_charWidth; - - boolean av_validCharWidth, av_isShowSeqFeatureHeight; - - private Integer currentColour; + private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite + .getInstance(AlphaComposite.SRC_OVER, 1.0f); /** * Constructor given a viewport @@ -63,273 +47,252 @@ public class FeatureRenderer extends FeatureRendererModel this.av = viewport; } - protected void updateAvConfig() + /** + * Renders the sequence using the given feature colour between the given start + * and end columns. Returns true if at least one column is drawn, else false + * (the feature range does not overlap the start and end positions). + * + * @param g + * @param seq + * @param featureStart + * @param featureEnd + * @param featureColour + * @param start + * @param end + * @param y1 + * @param colourOnly + * @return + */ + boolean renderFeature(Graphics g, SequenceI seq, int featureStart, + int featureEnd, Color featureColour, int start, int end, int y1, + boolean colourOnly) { - av_charHeight = av.getCharHeight(); - av_charWidth = av.getCharWidth(); - av_validCharWidth = av.isValidCharWidth(); - av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight(); - } + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + boolean validCharWidth = av.isValidCharWidth(); - void renderFeature(Graphics g, SequenceI seq, int fstart, int fend, - Color featureColour, int start, int end, int y1) - { - updateAvConfig(); - if (((fstart <= end) && (fend >= start))) + if (featureStart > end || featureEnd < start) { - if (fstart < start) - { // fix for if the feature we have starts before the sequence start, - fstart = start; // but the feature end is still valid!! - } - - if (fend >= end) - { - fend = end; - } - int pady = (y1 + av_charHeight) - av_charHeight / 5; - for (i = fstart; i <= fend; i++) - { - s = seq.getCharAt(i); - - if (jalview.util.Comparison.isGap(s)) - { - continue; - } - - g.setColor(featureColour); - - g.fillRect((i - start) * av_charWidth, y1, av_charWidth, - av_charHeight); - - if (offscreenRender || !av_validCharWidth) - { - continue; - } - - g.setColor(Color.white); - charOffset = (av_charWidth - fm.charWidth(s)) / 2; - g.drawString(String.valueOf(s), charOffset - + (av_charWidth * (i - start)), pady); + return false; + } - } + if (featureStart < start) + { + featureStart = start; } - } + if (featureEnd >= end) + { + featureEnd = end; + } + int pady = (y1 + charHeight) - charHeight / 5; - void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend, - Color featureColour, int start, int end, int y1, byte[] bs) - { - updateAvConfig(); - if (((fstart <= end) && (fend >= start))) + FontMetrics fm = g.getFontMetrics(); + for (int i = featureStart; i <= featureEnd; i++) { - if (fstart < start) - { // fix for if the feature we have starts before the sequence start, - fstart = start; // but the feature end is still valid!! - } + char s = seq.getCharAt(i); - if (fend >= end) - { - fend = end; - } - int pady = (y1 + av_charHeight) - av_charHeight / 5; - int ystrt = 0, yend = av_charHeight; - if (bs[0] != 0) - { - // signed - zero is always middle of residue line. - if (bs[1] < 128) - { - yend = av_charHeight * (128 - bs[1]) / 512; - ystrt = av_charHeight - yend / 2; - } - else - { - ystrt = av_charHeight / 2; - yend = av_charHeight * (bs[1] - 128) / 512; - } - } - else + if (Comparison.isGap(s)) { - yend = av_charHeight * bs[1] / 255; - ystrt = av_charHeight - yend; - + continue; } - for (i = fstart; i <= fend; i++) - { - s = seq.getCharAt(i); - - if (jalview.util.Comparison.isGap(s)) - { - continue; - } - g.setColor(featureColour); - int x = (i - start) * av_charWidth; - g.drawRect(x, y1, av_charWidth, av_charHeight); - g.fillRect(x, y1 + ystrt, av_charWidth, yend); + g.setColor(featureColour); - if (offscreenRender || !av_validCharWidth) - { - continue; - } + g.fillRect((i - start) * charWidth, y1, charWidth, + charHeight); - g.setColor(Color.black); - charOffset = (av_charWidth - fm.charWidth(s)) / 2; - g.drawString(String.valueOf(s), charOffset - + (av_charWidth * (i - start)), pady); + if (colourOnly || !validCharWidth) + { + continue; } - } - } - - BufferedImage offscreenImage; - @Override - public Color findFeatureColour(Color initialCol, SequenceI seq, int res) - { - return new Color(findFeatureColour(initialCol.getRGB(), seq, res)); + g.setColor(Color.white); + int charOffset = (charWidth - fm.charWidth(s)) / 2; + g.drawString(String.valueOf(s), charOffset + + (charWidth * (i - start)), pady); + } + return true; } /** - * This is used by Structure Viewers and the Overview Window to get the - * feature colour of the rendered sequence, returned as an RGB value + * Renders the sequence using the given SCORE feature colour between the given + * start and end columns. Returns true if at least one column is drawn, else + * false (the feature range does not overlap the start and end positions). * - * @param defaultColour + * @param g * @param seq - * @param column + * @param fstart + * @param fend + * @param featureColour + * @param start + * @param end + * @param y1 + * @param bs + * @param colourOnly * @return */ - public synchronized int findFeatureColour(int defaultColour, - final SequenceI seq, int column) + boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart, + int fend, Color featureColour, int start, int end, int y1, + byte[] bs, boolean colourOnly) { - if (!av.isShowSequenceFeatures()) + if (fstart > end || fend < start) { - return defaultColour; + return false; } - SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures(); - if (seq != lastSeq) + if (fstart < start) + { // fix for if the feature we have starts before the sequence start, + fstart = start; // but the feature end is still valid!! + } + + if (fend >= end) + { + fend = end; + } + int charHeight = av.getCharHeight(); + int pady = (y1 + charHeight) - charHeight / 5; + int ystrt = 0, yend = charHeight; + if (bs[0] != 0) { - lastSeq = seq; - lastSequenceFeatures = sequenceFeatures; - if (lastSequenceFeatures != null) + // signed - zero is always middle of residue line. + if (bs[1] < 128) { - sfSize = lastSequenceFeatures.length; + yend = charHeight * (128 - bs[1]) / 512; + ystrt = charHeight - yend / 2; + } + else + { + ystrt = charHeight / 2; + yend = charHeight * (bs[1] - 128) / 512; } } else { - if (lastSequenceFeatures != sequenceFeatures) + yend = charHeight * bs[1] / 255; + ystrt = charHeight - yend; + + } + + FontMetrics fm = g.getFontMetrics(); + int charWidth = av.getCharWidth(); + + for (int i = fstart; i <= fend; i++) + { + char s = seq.getCharAt(i); + + if (Comparison.isGap(s)) { - lastSequenceFeatures = sequenceFeatures; - if (lastSequenceFeatures != null) - { - sfSize = lastSequenceFeatures.length; - } + continue; } + + g.setColor(featureColour); + int x = (i - start) * charWidth; + g.drawRect(x, y1, charWidth, charHeight); + g.fillRect(x, y1 + ystrt, charWidth, yend); + + if (colourOnly || !av.isValidCharWidth()) + { + continue; + } + + g.setColor(Color.black); + int charOffset = (charWidth - fm.charWidth(s)) / 2; + g.drawString(String.valueOf(s), charOffset + + (charWidth * (i - start)), pady); } + return true; + } - if (lastSequenceFeatures == null || sfSize == 0) + /** + * {@inheritDoc} + */ + @Override + public Color findFeatureColour(SequenceI seq, int column, Graphics g) + { + if (!av.isShowSequenceFeatures()) { - return defaultColour; + return null; } - if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column))) + SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures(); + + if (sequenceFeatures == null || sequenceFeatures.length == 0) { - return Color.white.getRGB(); + return null; } - // Only bother making an offscreen image if transparency is applied - if (transparency != 1.0f && offscreenImage == null) + if (Comparison.isGap(seq.getCharAt(column))) { - offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + return Color.white; } - currentColour = null; - // TODO: non-threadsafe - each rendering thread needs its own instance of - // the feature renderer - or this should be synchronized. - offscreenRender = true; - - if (offscreenImage != null) + Color renderedColour = null; + if (transparency == 1.0f) { - offscreenImage.setRGB(0, 0, defaultColour); - drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0); - - return offscreenImage.getRGB(0, 0); + /* + * simple case - just find the topmost rendered visible feature colour + */ + renderedColour = findFeatureColour(seq, seq.findPosition(column)); } else { - drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1); - - if (currentColour == null) - { - return defaultColour; - } - else - { - return currentColour.intValue(); - } + /* + * transparency case - draw all visible features in render order to + * build up a composite colour on the graphics context + */ + renderedColour = drawSequence(g, seq, column, column, 0, true); } - + return renderedColour; } - private volatile SequenceFeature[] lastSequenceFeatures; - - int sfSize; - - int sfindex; - - int spos; - - int epos; - /** - * Draws the sequence on the graphics context, or just determines the colour - * that would be drawn (if flag offscreenrender is true). + * Draws the sequence features on the graphics context, or just determines the + * colour that would be drawn (if flag colourOnly is true). Returns the last + * colour drawn (which may not be the effective colour if transparency + * applies), or null if no feature is drawn in the range given. * * @param g + * the graphics context to draw on (may be null if colourOnly==true) * @param seq * @param start - * start column (or sequence position in offscreenrender mode) + * start column * @param end - * end column (not used in offscreenrender mode) + * end column * @param y1 * vertical offset at which to draw on the graphics + * @param colourOnly + * if true, only do enough to determine the colour for the position, + * do not draw the character + * @return */ - public synchronized void drawSequence(Graphics g, final SequenceI seq, - int start, int end, int y1) + public synchronized Color drawSequence(final Graphics g, + final SequenceI seq, int start, int end, int y1, + boolean colourOnly) { SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures(); if (sequenceFeatures == null || sequenceFeatures.length == 0) { - return; - } - - if (g != null) - { - fm = g.getFontMetrics(); + return null; } updateFeatures(); - if (lastSeq == null || seq != lastSeq - || sequenceFeatures != lastSequenceFeatures) - { - lastSeq = seq; - lastSequenceFeatures = sequenceFeatures; - } - - if (transparency != 1 && g != null) + if (transparency != 1f && g != null) { Graphics2D g2 = (Graphics2D) g; g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transparency)); } - if (!offscreenRender) - { - spos = lastSeq.findPosition(start); - epos = lastSeq.findPosition(end); - } + int startPos = seq.findPosition(start); + int endPos = seq.findPosition(end); + + int sfSize = sequenceFeatures.length; + Color drawnColour = null; - sfSize = lastSequenceFeatures.length; + /* + * iterate over features in ordering of their rendering (last is on top) + */ for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++) { String type = renderOrder[renderIndex]; @@ -340,27 +303,29 @@ public class FeatureRenderer extends FeatureRendererModel // loop through all features in sequence to find // current feature to render - for (sfindex = 0; sfindex < sfSize; sfindex++) + for (int sfindex = 0; sfindex < sfSize; sfindex++) { - final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex]; + final SequenceFeature sequenceFeature = sequenceFeatures[sfindex]; if (!sequenceFeature.type.equals(type)) { continue; } + /* + * a feature type may be flagged as shown but the group + * an instance of it belongs to may be hidden + */ if (featureGroupNotShown(sequenceFeature)) { continue; } /* - * check feature overlaps the visible part of the alignment, - * unless doing offscreenRender (to the Overview window or a - * structure viewer) which is not limited + * check feature overlaps the target range + * TODO: efficient retrieval of features overlapping a range */ - if (!offscreenRender - && (sequenceFeature.getBegin() > epos || sequenceFeature - .getEnd() < spos)) + if (sequenceFeature.getBegin() > endPos + || sequenceFeature.getEnd() < startPos) { continue; } @@ -368,58 +333,46 @@ public class FeatureRenderer extends FeatureRendererModel Color featureColour = getColour(sequenceFeature); boolean isContactFeature = sequenceFeature.isContactFeature(); - if (offscreenRender && offscreenImage == null) - { - /* - * offscreen mode with no image (image is only needed if transparency - * is applied to feature colours) - just check feature is rendered at - * the requested position (start == sequence position in this mode) - */ - boolean featureIsAtPosition = sequenceFeature.begin <= start - && sequenceFeature.end >= start; - if (isContactFeature) - { - featureIsAtPosition = sequenceFeature.begin == start - || sequenceFeature.end == start; - } - if (featureIsAtPosition) - { - // this is passed out to the overview and other sequence renderers - // (e.g. molecule viewer) to get displayed colour for rendered - // sequence - currentColour = new Integer(featureColour.getRGB()); - // used to be retreived from av.featuresDisplayed - // currentColour = av.featuresDisplayed - // .get(sequenceFeatures[sfindex].type); - - } - } - else if (isContactFeature) + if (isContactFeature) { - renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1, + boolean drawn = renderFeature(g, seq, + seq.findIndex(sequenceFeature.begin) - 1, seq.findIndex(sequenceFeature.begin) - 1, featureColour, - start, end, y1); - renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1, + start, end, y1, colourOnly); + drawn |= renderFeature(g, seq, + seq.findIndex(sequenceFeature.end) - 1, seq.findIndex(sequenceFeature.end) - 1, featureColour, - start, end, y1); - + start, end, y1, colourOnly); + if (drawn) + { + drawnColour = featureColour; + } } else if (showFeature(sequenceFeature)) { - if (av_isShowSeqFeatureHeight + if (av.isShowSequenceFeaturesHeight() && !Float.isNaN(sequenceFeature.score)) { - renderScoreFeature(g, seq, + boolean drawn = renderScoreFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1, - seq.findIndex(sequenceFeature.end) - 1, - featureColour, start, end, y1, - normaliseScore(sequenceFeature)); + seq.findIndex(sequenceFeature.end) - 1, featureColour, + start, end, y1, normaliseScore(sequenceFeature), + colourOnly); + if (drawn) + { + drawnColour = featureColour; + } } else { - renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1, - seq.findIndex(sequenceFeature.end) - 1, - featureColour, start, end, y1); + boolean drawn = renderFeature(g, seq, + seq.findIndex(sequenceFeature.begin) - 1, + seq.findIndex(sequenceFeature.end) - 1, featureColour, + start, end, y1, colourOnly); + if (drawn) + { + drawnColour = featureColour; + } } } } @@ -427,10 +380,14 @@ public class FeatureRenderer extends FeatureRendererModel if (transparency != 1.0f && g != null) { + /* + * reset transparency + */ Graphics2D g2 = (Graphics2D) g; - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - 1.0f)); + g2.setComposite(NO_TRANSPARENCY); } + + return drawnColour; } /** @@ -459,7 +416,78 @@ public class FeatureRenderer extends FeatureRendererModel @Override public void featuresAdded() { - lastSeq = null; findAllFeatures(); } + + /** + * Returns the sequence feature colour rendered at the given sequence + * position, or null if none found. The feature of highest render order (i.e. + * on top) is found, subject to both feature type and feature group being + * visible, and its colour returned. + * + * @param seq + * @param pos + * @return + */ + Color findFeatureColour(SequenceI seq, int pos) + { + SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures(); + if (sequenceFeatures == null || sequenceFeatures.length == 0) + { + return null; + } + + /* + * check for new feature added while processing + */ + updateFeatures(); + + /* + * inspect features in reverse renderOrder (the last in the array is + * displayed on top) until we find one that is rendered at the position + */ + for (int renderIndex = renderOrder.length - 1; renderIndex >= 0; renderIndex--) + { + String type = renderOrder[renderIndex]; + if (!showFeatureOfType(type)) + { + continue; + } + + for (int sfindex = 0; sfindex < sequenceFeatures.length; sfindex++) + { + SequenceFeature sequenceFeature = sequenceFeatures[sfindex]; + if (!sequenceFeature.type.equals(type)) + { + continue; + } + + if (featureGroupNotShown(sequenceFeature)) + { + continue; + } + + /* + * check the column position is within the feature range + * (or is one of the two contact positions for a contact feature) + */ + boolean featureIsAtPosition = sequenceFeature.begin <= pos + && sequenceFeature.end >= pos; + if (sequenceFeature.isContactFeature()) + { + featureIsAtPosition = sequenceFeature.begin == pos + || sequenceFeature.end == pos; + } + if (featureIsAtPosition) + { + return getColour(sequenceFeature); + } + } + } + + /* + * no displayed feature found at position + */ + return null; + } } diff --git a/src/jalview/schemes/ColourSchemeLoader.java b/src/jalview/schemes/ColourSchemeLoader.java new file mode 100644 index 0000000..8660f3e --- /dev/null +++ b/src/jalview/schemes/ColourSchemeLoader.java @@ -0,0 +1,125 @@ +package jalview.schemes; + +import jalview.binding.JalviewUserColours; + +import java.awt.Color; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; + +import org.exolab.castor.xml.Unmarshaller; + +public class ColourSchemeLoader +{ + + /** + * Loads a user defined colour scheme from file. The file should contain a + * definition of residue colours in XML format as defined in + * JalviewUserColours.xsd. + * + * @param filePath + * + * @return + */ + public static UserColourScheme loadColourScheme(String filePath) + { + UserColourScheme ucs = null; + Color[] newColours = null; + File file = new File(filePath); + try + { + InputStreamReader in = new InputStreamReader( + new FileInputStream(file), "UTF-8"); + + jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours(); + + org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller( + jucs); + jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar + .unmarshal(in); + + /* + * non-case-sensitive colours are for 20 amino acid codes, + * B, Z, X and Gap + * optionally, lower-case alternatives for all except Gap + */ + newColours = new Color[24]; + Color[] lowerCase = new Color[23]; + boolean caseSensitive = false; + + String name; + int index; + for (int i = 0; i < jucs.getColourCount(); i++) + { + name = jucs.getColour(i).getName(); + if (ResidueProperties.aa3Hash.containsKey(name)) + { + index = ResidueProperties.aa3Hash.get(name).intValue(); + } + else + { + index = ResidueProperties.aaIndex[name.charAt(0)]; + } + if (index == -1) + { + continue; + } + + Color color = new Color(Integer.parseInt(jucs.getColour(i) + .getRGB(), 16)); + if (name.toLowerCase().equals(name)) + { + caseSensitive = true; + lowerCase[index] = color; + } + else + { + newColours[index] = color; + } + } + + /* + * instantiate the colour scheme + */ + ucs = new UserColourScheme(newColours); + ucs.setName(jucs.getSchemeName()); + if (caseSensitive) + { + ucs.setLowerCaseColours(lowerCase); + } + } catch (Exception ex) + { + // Could be old Jalview Archive format + try + { + InputStreamReader in = new InputStreamReader(new FileInputStream( + file), "UTF-8"); + + jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours(); + + jucs = JalviewUserColours.unmarshal(in); + + newColours = new Color[jucs.getColourCount()]; + + for (int i = 0; i < 24; i++) + { + newColours[i] = new Color(Integer.parseInt(jucs.getColour(i) + .getRGB(), 16)); + } + ucs = new UserColourScheme(newColours); + ucs.setName(jucs.getSchemeName()); + } catch (Exception ex2) + { + ex2.printStackTrace(); + } + + if (newColours == null) + { + System.out.println("Error loading User ColourFile\n" + ex); + } + } + + return ucs; + } + +} diff --git a/src/jalview/schemes/ColourSchemes.java b/src/jalview/schemes/ColourSchemes.java index 817fb01..dc7e403 100644 --- a/src/jalview/schemes/ColourSchemes.java +++ b/src/jalview/schemes/ColourSchemes.java @@ -1,14 +1,9 @@ package jalview.schemes; -import jalview.binding.JalviewUserColours; import jalview.datamodel.AnnotatedCollectionI; import jalview.datamodel.SequenceCollectionI; import jalview.datamodel.SequenceI; -import java.awt.Color; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; import java.util.LinkedHashMap; import java.util.Map; @@ -177,114 +172,4 @@ public class ColourSchemes } return false; } - - /** - * Loads a user defined colour scheme from file. The file should contain a - * definition of residue colours in XML format as defined in - * JalviewUserColours.xsd. - * - * @param filePath - * - * @return - */ - public static UserColourScheme loadColourScheme(String filePath) - { - UserColourScheme ucs = null; - Color[] newColours = null; - File file = new File(filePath); - try - { - InputStreamReader in = new InputStreamReader( - new FileInputStream(file), "UTF-8"); - - jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours(); - - org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller( - jucs); - jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar - .unmarshal(in); - - /* - * non-case-sensitive colours are for 20 amino acid codes, - * B, Z, X and Gap - * optionally, lower-case alternatives for all except Gap - */ - newColours = new Color[24]; - Color[] lowerCase = new Color[23]; - boolean caseSensitive = false; - - String name; - int index; - for (int i = 0; i < jucs.getColourCount(); i++) - { - name = jucs.getColour(i).getName(); - if (ResidueProperties.aa3Hash.containsKey(name)) - { - index = ResidueProperties.aa3Hash.get(name).intValue(); - } - else - { - index = ResidueProperties.aaIndex[name.charAt(0)]; - } - if (index == -1) - { - continue; - } - - Color color = new Color(Integer.parseInt(jucs.getColour(i) - .getRGB(), 16)); - if (name.toLowerCase().equals(name)) - { - caseSensitive = true; - lowerCase[index] = color; - } - else - { - newColours[index] = color; - } - } - - /* - * instantiate the colour scheme - */ - ucs = new UserColourScheme(newColours); - ucs.setName(jucs.getSchemeName()); - if (caseSensitive) - { - ucs.setLowerCaseColours(lowerCase); - } - } catch (Exception ex) - { - // Could be old Jalview Archive format - try - { - InputStreamReader in = new InputStreamReader(new FileInputStream( - file), "UTF-8"); - - jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours(); - - jucs = JalviewUserColours.unmarshal(in); - - newColours = new Color[jucs.getColourCount()]; - - for (int i = 0; i < 24; i++) - { - newColours[i] = new Color(Integer.parseInt(jucs.getColour(i) - .getRGB(), 16)); - } - ucs = new UserColourScheme(newColours); - ucs.setName(jucs.getSchemeName()); - } catch (Exception ex2) - { - ex2.printStackTrace(); - } - - if (newColours == null) - { - System.out.println("Error loading User ColourFile\n" + ex); - } - } - - return ucs; - } } diff --git a/src/jalview/schemes/ResidueProperties.java b/src/jalview/schemes/ResidueProperties.java index 406814a..1e6142d 100755 --- a/src/jalview/schemes/ResidueProperties.java +++ b/src/jalview/schemes/ResidueProperties.java @@ -486,7 +486,7 @@ public class ResidueProperties -3, 3, 0, -1, -4 }, { -2, -2, 1, 6, -3, 0, 2, -1, -1, -3, -4, -1, -3, -3, -1, 0, -1, -4, -3, -3, 4, 1, -1, -4 }, - { 0, 3, -3, -3, 9, -3, -4, -3, -3, -1, -1, -3, -1, -2, -3, -1, -1, + { 0, -3, -3, -3, 9, -3, -4, -3, -3, -1, -1, -3, -1, -2, -3, -1, -1, -2, -2, -1, -3, -3, -2, -4 }, { -1, 1, 0, 0, -3, 5, 2, -2, 0, -3, -2, 1, 0, -3, -1, 0, -1, -2, -1, -2, 0, 3, -1, -4 }, diff --git a/src/jalview/schemes/ScoreMatrix.java b/src/jalview/schemes/ScoreMatrix.java index 5e5fa8d..d82f54c 100644 --- a/src/jalview/schemes/ScoreMatrix.java +++ b/src/jalview/schemes/ScoreMatrix.java @@ -21,10 +21,10 @@ package jalview.schemes; import jalview.analysis.scoremodels.PairwiseSeqScoreModel; -import jalview.api.analysis.ScoreModelI; +import jalview.math.Matrix; +import jalview.math.MatrixI; -public class ScoreMatrix extends PairwiseSeqScoreModel implements - ScoreModelI +public class ScoreMatrix extends PairwiseSeqScoreModel { String name; @@ -79,19 +79,21 @@ public class ScoreMatrix extends PairwiseSeqScoreModel implements } /** + * Answers the score for substituting first char in A1 with first char in A2 * * @param A1 * @param A2 - * @return score for substituting first char in A1 with first char in A2 + * @return */ public int getPairwiseScore(String A1, String A2) { return getPairwiseScore(A1.charAt(0), A2.charAt(0)); } + @Override public int getPairwiseScore(char c, char d) { - int pog = 0; + int score = 0; try { @@ -99,19 +101,19 @@ public class ScoreMatrix extends PairwiseSeqScoreModel implements : ResidueProperties.nucleotideIndex[c]; int b = (type == 0) ? ResidueProperties.aaIndex[d] : ResidueProperties.nucleotideIndex[d]; - - pog = matrix[a][b]; + score = matrix[a][b]; } catch (Exception e) { // System.out.println("Unknown residue in " + A1 + " " + A2); } - return pog; + return score; } /** * pretty print the matrix */ + @Override public String toString() { return outputMatrix(false); @@ -170,4 +172,47 @@ public class ScoreMatrix extends PairwiseSeqScoreModel implements } return sb.toString(); } + + /** + * Computes an NxN matrix where N is the number of sequences, and entry [i, j] + * is sequence[i] pairwise multiplied with sequence[j], as a sum of scores + * computed using the current score matrix. For example + *

      + *
    • Sequences:
    • + *
    • FKL
    • + *
    • R-D
    • + *
    • QIA
    • + *
    • GWC
    • + *
    • Score matrix is BLOSUM62
    • + *
    • Gaps treated same as X (unknown)
    • + *
    • product [0, 0] = F.F + K.K + L.L = 6 + 5 + 4 = 15
    • + *
    • product [1, 1] = R.R + -.- + D.D = 5 + -1 + 6 = 10
    • + *
    • product [2, 2] = Q.Q + I.I + A.A = 5 + 4 + 4 = 13
    • + *
    • product [3, 3] = G.G + W.W + C.C = 6 + 11 + 9 = 26
    • + *
    • product[0, 1] = F.R + K.- + L.D = -3 + -1 + -3 = -8 + *
    • and so on
    • + *
    + */ + public MatrixI computePairwiseScores(String[] seqs) + { + double[][] values = new double[seqs.length][]; + for (int row = 0; row < seqs.length; row++) + { + values[row] = new double[seqs.length]; + for (int col = 0; col < seqs.length; col++) + { + int total = 0; + int width = Math.min(seqs[row].length(), seqs[col].length()); + for (int i = 0; i < width; i++) + { + char c1 = seqs[row].charAt(i); + char c2 = seqs[col].charAt(i); + int score = getPairwiseScore(c1, c2); + total += score; + } + values[row][col] = total; + } + } + return new Matrix(values); + } } diff --git a/src/jalview/structure/AtomSpec.java b/src/jalview/structure/AtomSpec.java index a19acef..f20cd31 100644 --- a/src/jalview/structure/AtomSpec.java +++ b/src/jalview/structure/AtomSpec.java @@ -29,9 +29,8 @@ package jalview.structure; */ public class AtomSpec { - // TODO clarify do we want pdbFile here, or pdbId? - // compare highlightAtom in 2.8.2 for JalviewJmolBinding and - // javascript.MouseOverStructureListener + int modelNo; + private String pdbFile; private String chain; @@ -41,6 +40,60 @@ public class AtomSpec private int atomIndex; /** + * Parses a Chimera atomspec e.g. #1:12.A to construct an AtomSpec model (with + * null pdb file name) + * + * @param spec + * @return + * @throw IllegalArgumentException if the spec cannot be parsed, or represents + * more than one residue + */ + public static AtomSpec fromChimeraAtomspec(String spec) + { + int colonPos = spec.indexOf(":"); + if (colonPos == -1) + { + throw new IllegalArgumentException(spec); + } + + int hashPos = spec.indexOf("#"); + if (hashPos == -1 && colonPos != 0) + { + // # is missing but something precedes : - reject + throw new IllegalArgumentException(spec); + } + + String modelSubmodel = spec.substring(hashPos + 1, colonPos); + int dotPos = modelSubmodel.indexOf("."); + int modelId = 0; + try + { + modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel + : modelSubmodel.substring(0, dotPos)); + } catch (NumberFormatException e) + { + // ignore, default to model 0 + } + + String residueChain = spec.substring(colonPos + 1); + dotPos = residueChain.indexOf("."); + int resNum = 0; + try + { + resNum = Integer.parseInt(dotPos == -1 ? residueChain + : residueChain.substring(0, dotPos)); + } catch (NumberFormatException e) + { + // could be a range e.g. #1:4-7.B + throw new IllegalArgumentException(spec); + } + + String chainId = dotPos == -1 ? "" : residueChain.substring(dotPos + 1); + + return new AtomSpec(modelId, chainId, resNum, 0); + } + + /** * Constructor * * @param pdbFile @@ -56,6 +109,22 @@ public class AtomSpec this.atomIndex = atomNo; } + /** + * Constructor + * + * @param modelId + * @param chainId + * @param resNo + * @param atomNo + */ + public AtomSpec(int modelId, String chainId, int resNo, int atomNo) + { + this.modelNo = modelId; + this.chain = chainId; + this.pdbResNum = resNo; + this.atomIndex = atomNo; + } + public String getPdbFile() { return pdbFile; @@ -76,6 +145,16 @@ public class AtomSpec return atomIndex; } + public int getModelNumber() + { + return modelNo; + } + + public void setPdbFile(String file) + { + pdbFile = file; + } + @Override public String toString() { diff --git a/src/jalview/structure/StructureMapping.java b/src/jalview/structure/StructureMapping.java index 78634e0..40789ed 100644 --- a/src/jalview/structure/StructureMapping.java +++ b/src/jalview/structure/StructureMapping.java @@ -23,7 +23,9 @@ package jalview.structure; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.SequenceI; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; public class StructureMapping { @@ -47,6 +49,18 @@ public class StructureMapping // and atomNo HashMap mapping; + /** + * Constructor + * + * @param seq + * @param pdbfile + * @param pdbid + * @param chain + * @param mapping + * a map from sequence to two values, { resNo, atomNo } in the + * structure + * @param mappingDetails + */ public StructureMapping(SequenceI seq, String pdbfile, String pdbid, String chain, HashMap mapping, String mappingDetails) @@ -111,6 +125,70 @@ public class StructureMapping } /** + * Returns a (possibly empty) list of [start, end] residue positions in the + * mapped structure, corresponding to the given range of sequence positions + * + * @param fromSeqPos + * @param toSeqPos + * @return + */ + public List getPDBResNumRanges(int fromSeqPos, int toSeqPos) + { + List result = new ArrayList(); + int startRes = -1; + int endRes = -1; + + for (int i = fromSeqPos; i <= toSeqPos; i++) + { + int resNo = getPDBResNum(i); + if (resNo == UNASSIGNED_VALUE) + { + continue; // no mapping from this sequence position + } + if (startRes == -1) + { + startRes = resNo; + endRes = resNo; + } + if (resNo >= startRes && resNo <= endRes) + { + // within the current range - no change + continue; + } + if (resNo == startRes - 1) + { + // extend beginning of current range + startRes--; + continue; + } + if (resNo == endRes + 1) + { + // extend end of current range + endRes++; + continue; + } + + /* + * resNo is not within or contiguous with last range, + * so write out the last range + */ + result.add(new int[] { startRes, endRes }); + startRes = resNo; + endRes = resNo; + } + + /* + * and add the last range + */ + if (startRes != -1) + { + result.add(new int[] { startRes, endRes }); + } + + return result; + } + + /** * * @param pdbResNum * @return -1 or the corresponding sequence position for a pdb residue number diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index 65fd5e7..3ab642f 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -589,11 +589,9 @@ public class StructureSelectionManager return pdb; } - private boolean isCIFFile(String filename) + public void addStructureMapping(StructureMapping sm) { - String fileExt = filename.substring(filename.lastIndexOf(".") + 1, - filename.length()); - return "cif".equalsIgnoreCase(fileExt); + mappings.add(sm); } /** @@ -806,6 +804,27 @@ public class StructureSelectionManager return; } + SearchResultsI results = findAlignmentPositionsForStructurePositions(atoms); + for (Object li : listeners) + { + if (li instanceof SequenceListener) + { + ((SequenceListener) li).highlightSequence(results); + } + } + } + + /** + * Constructs a SearchResults object holding regions (if any) in the Jalview + * alignment which have a mapping to the structure viewer positions in the + * supplied list + * + * @param atoms + * @return + */ + public SearchResultsI findAlignmentPositionsForStructurePositions( + List atoms) + { SearchResultsI results = new SearchResults(); for (AtomSpec atom : atoms) { @@ -831,13 +850,7 @@ public class StructureSelectionManager } } } - for (Object li : listeners) - { - if (li instanceof SequenceListener) - { - ((SequenceListener) li).highlightSequence(results); - } - } + return results; } /** diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java index fda08fd..84475fe 100644 --- a/src/jalview/structures/models/AAStructureBindingModel.java +++ b/src/jalview/structures/models/AAStructureBindingModel.java @@ -21,7 +21,6 @@ package jalview.structures.models; import jalview.api.AlignmentViewPanel; -import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.api.StructureSelectionManagerProvider; import jalview.api.structures.JalviewStructureDisplayI; @@ -42,6 +41,7 @@ import jalview.util.MessageManager; import java.awt.Color; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.List; /** @@ -521,15 +521,15 @@ public abstract class AAStructureBindingModel extends * the sequence alignment which is the basis of structure * superposition * @param matched - * an array of booleans, indexed by alignment column, where true - * indicates that every structure has a mapped residue present in the - * column (so the column can participate in structure alignment) + * a BitSet, where bit j is set to indicate that every structure has + * a mapped residue present in column j (so the column can + * participate in structure alignment) * @param structures * an array of data beans corresponding to pdb file index * @return */ protected int findSuperposableResidues(AlignmentI alignment, - boolean[] matched, SuperposeData[] structures) + BitSet matched, SuperposeData[] structures) { int refStructure = -1; String[] files = getPdbFile(); @@ -559,16 +559,16 @@ public abstract class AAStructureBindingModel extends { refStructure = pdbfnum; } - for (int r = 0; r < matched.length; r++) + for (int r = 0; r < alignment.getWidth(); r++) { - if (!matched[r]) + if (!matched.get(r)) { continue; } int pos = getMappedPosition(theSequence, r, mapping); if (pos < 1 || pos == lastPos) { - matched[r] = false; + matched.clear(r); continue; } lastPos = pos; @@ -700,24 +700,29 @@ public abstract class AAStructureBindingModel extends public abstract void setJalviewColourScheme(ColourSchemeI cs); - public abstract void superposeStructures(AlignmentI[] als, int[] alm, - ColumnSelection[] alc); - - public abstract void setBackgroundColour(Color col); - - protected abstract StructureMappingcommandSet[] getColourBySequenceCommands( - String[] files, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment); - /** - * returns the current featureRenderer that should be used to colour the - * structures - * - * @param alignment + * Constructs and sends a command to align structures against a reference + * structure, based on one or more sequence alignments. May optionally return + * an error or warning message for the alignment command. * + * @param alignments + * an array of alignments to process + * @param structureIndices + * an array of corresponding reference structures (index into pdb + * file array); if a negative value is passed, the first PDB file + * mapped to an alignment sequence is used as the reference for + * superposition + * @param hiddenCols + * an array of corresponding hidden columns for each alignment * @return */ - public abstract FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment); + public abstract String superposeStructures(AlignmentI[] alignments, int[] structureIndices, + ColumnSelection[] hiddenCols); + + public abstract void setBackgroundColour(Color col); + + protected abstract StructureMappingcommandSet[] getColourBySequenceCommands( + String[] files, SequenceRenderer sr, AlignmentViewPanel avp); /** * returns the current sequenceRenderer that should be used to colour the @@ -743,8 +748,6 @@ public abstract class AAStructureBindingModel extends */ public void colourBySequence(AlignmentViewPanel alignmentv) { - boolean showFeatures = alignmentv.getAlignViewport() - .isShowSequenceFeatures(); if (!colourBySequence || !isLoadingFinished()) { return; @@ -757,15 +760,8 @@ public abstract class AAStructureBindingModel extends SequenceRenderer sr = getSequenceRenderer(alignmentv); - FeatureRenderer fr = null; - if (showFeatures) - { - fr = getFeatureRenderer(alignmentv); - } - AlignmentI alignment = alignmentv.getAlignment(); - StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands( - files, sr, fr, alignment); + files, sr, alignmentv); colourBySequence(colourBySequenceCommands); } @@ -773,4 +769,7 @@ public abstract class AAStructureBindingModel extends { return fileLoadingError != null && fileLoadingError.length() > 0; } + + public abstract jalview.api.FeatureRenderer getFeatureRenderer( + AlignmentViewPanel alignment); } diff --git a/src/jalview/urls/CustomUrlProvider.java b/src/jalview/urls/CustomUrlProvider.java new file mode 100644 index 0000000..07f4068 --- /dev/null +++ b/src/jalview/urls/CustomUrlProvider.java @@ -0,0 +1,342 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview 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 3 + * of the License, or (at your option) any later version. + * + * Jalview 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 Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ + +package jalview.urls; + +import static jalview.util.UrlConstants.DB_ACCESSION; +import static jalview.util.UrlConstants.DELIM; +import static jalview.util.UrlConstants.SEP; +import static jalview.util.UrlConstants.SEQUENCE_ID; + +import jalview.util.MessageManager; +import jalview.util.UrlConstants; +import jalview.util.UrlLink; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.StringTokenizer; + +/** + * + * Implements the UrlProviderI interface for a UrlProvider object which serves + * custom URLs defined by the user + * + * @author $author$ + * @version $Revision$ + */ +public class CustomUrlProvider extends UrlProviderImpl +{ + // Default sequence URL link label for SRS + private static final String SRS_LABEL = "SRS"; + + // map of string ids to urlLinks (selected) + private HashMap selectedUrls; + + // map of string ids to urlLinks (not selected) + private HashMap nonselectedUrls; + + /** + * Construct UrlProvider for custom (user-entered) URLs + * + * @param inMenuUrlList + * list of URLs set to be displayed in menu, in form stored in Cache. + * i.e. SEP delimited string + * @param storedUrlList + * list of custom URLs entered by user but not currently displayed in + * menu, in form stored in Cache + */ + public CustomUrlProvider(String inMenuUrlList, String storedUrlList) + { + try + { + selectedUrls = parseUrlStrings(inMenuUrlList); + nonselectedUrls = parseUrlStrings(storedUrlList); + } catch (Exception ex) + { + System.out + .println(ex.getMessage() + "\nError parsing sequence links"); + } + } + + /** + * Construct UrlProvider for custom (user-entered) URLs + * + * @param urlList + * list of URLs to be displayed in menu, as (label,url) pairs + * @param storedUrlList + * list of custom URLs entered by user but not currently displayed in + * menu, as (label,url) pairs + */ + public CustomUrlProvider(Map inMenuUrlList, + Map storedUrlList) + { + try + { + selectedUrls = parseUrlList(inMenuUrlList); + nonselectedUrls = parseUrlList(storedUrlList); + } catch (Exception ex) + { + System.out + .println(ex.getMessage() + "\nError parsing sequence links"); + } + } + + private HashMap parseUrlStrings(String urlStrings) + { + // cachedUrlList is in form