From: gmungoc Date: Mon, 3 Apr 2017 13:13:46 +0000 (+0100) Subject: Merge branch 'develop' into bug/JAL-2314 X-Git-Tag: Release_2_10_2~3^2~140 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=667623e455de6756776d445c800d39d93fa060e2;hp=8f9537bd5d7f4455c6309569e2ca260463b7d2e4;p=jalview.git Merge branch 'develop' into bug/JAL-2314 --- diff --git a/help/html/colourSchemes/user.html b/help/html/colourSchemes/user.html index fb6c356..c2fde1c 100755 --- a/help/html/colourSchemes/user.html +++ b/help/html/colourSchemes/user.html @@ -27,7 +27,7 @@ User Defined Colours

- +

You may define any number of new colour schemes, each with a unique @@ -41,7 +41,7 @@ The Case Sensitive option allows you to choose distinct colours for upper and lower case residue codes.

- The Lower Case Colour option allows you to apply a selected colour + The Colour All Lower Case option allows you to apply a selected colour to all lower case residues.

Click Apply or OK to set your new @@ -56,11 +56,5 @@
Any saved colour schemes will be automatically loaded the next time you use Jalview.
-
- Note: the screenshot shows the appearance when running Java - version 6. For Java 7 (from Jalview 2.8.2) only the Swatches colour - chooser is currently supported (for reasons of available screen - space). -

diff --git a/help/html/colourSchemes/userDefined_java6.gif b/help/html/colourSchemes/userDefined_java6.gif deleted file mode 100644 index d737e80..0000000 Binary files a/help/html/colourSchemes/userDefined_java6.gif and /dev/null differ diff --git a/help/html/colourSchemes/userDefined_java7.gif b/help/html/colourSchemes/userDefined_java7.gif index f0ced11..de80b84 100644 Binary files a/help/html/colourSchemes/userDefined_java7.gif and b/help/html/colourSchemes/userDefined_java7.gif differ diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index d6d3034..f284ff9 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -68,7 +68,6 @@ action.show_gaps = Show Gaps action.show_hidden_markers = Show Hidden Markers action.find = Find action.undefine_groups = Undefine Groups -action.create_groups = Create Groups action.make_groups_selection = Make Groups For Selection action.copy = Copy action.cut = Cut @@ -669,8 +668,7 @@ label.2d_rna_sequence_name = 2D RNA - {0} label.edit_name_and_description_current_group = Edit name and description of current group label.from_file = From File label.enter_pdb_id = Enter PDB Id (or pdbid:chaincode) -label.text_colour = Text Colour -action.set_text_colour = Text Colour... +label.text_colour = Text Colour... label.structure = Structure label.show_pdbstruct_dialog = 3D Structure Data... label.view_rna_structure = VARNA 2D Structure diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 6b8761e..a878ab7 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -66,7 +66,6 @@ action.show_gaps = Mostrar huecos action.show_hidden_markers = Mostrar marcadores ocultos action.find = Buscar action.undefine_groups = Grupos sin definir -action.create_groups = Crear grupos action.make_groups_selection = Hacer grupos para seleccionar action.copy = Copiar action.cut = Cortar @@ -621,7 +620,7 @@ label.2d_rna_sequence_name = 2D RNA - {0} label.edit_name_and_description_current_group = Editar el nombre y la descripción del grupo actual label.from_file = desde fichero label.enter_pdb_id = Introducir PDB Id -label.text_colour = Color del texto +label.text_colour = Color de texto... label.structure = Estructura label.create_sequence_details_report_annotation_for = Anotación para {0} label.sequence_details_for = Detalles de la secuencia para {0} @@ -1158,7 +1157,6 @@ label.invalid_search=Texto de b action.export_annotations=Exportar Anotaciones action.set_as_reference=Marcar como Referencia action.unmark_as_reference=Desmarcar como Referencia -action.set_text_colour=Color de Texto... label.chimera_failed=Error al abrir Chimera - está instalado?\nCompruebe ruta en Preferencias, Estructura label.find=Buscar label.select_pdb_file=Seleccionar Fichero PDB @@ -1288,4 +1286,4 @@ 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 \ No newline at end of file +label.urllinks = Enlaces diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index e51131b..65c4fef 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -90,6 +90,7 @@ import java.awt.Label; import java.awt.Menu; import java.awt.MenuBar; import java.awt.MenuItem; +import java.awt.MenuShortcut; import java.awt.Panel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -1071,6 +1072,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, { delete_actionPerformed(); } + else if (source == createGroup) + { + createGroup_actionPerformed(); + } + else if (source == unGroup) + { + unGroup_actionPerformed(); + } else if (source == grpsFromSelection) { makeGrpsFromSelection_actionPerformed(); @@ -3337,7 +3346,10 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, .getString("action.make_groups_selection")); grpsFromSelection.addActionListener(this); createGroup.setLabel(MessageManager.getString("action.create_group")); + createGroup.addActionListener(this); unGroup.setLabel(MessageManager.getString("action.remove_group")); + unGroup.addActionListener(this); + annotationColumnSelection.setLabel(MessageManager .getString("action.select_by_annotation")); annotationColumnSelection.addActionListener(this); diff --git a/src/jalview/appletgui/AnnotationColourChooser.java b/src/jalview/appletgui/AnnotationColourChooser.java index 487b75c..f516bc9 100644 --- a/src/jalview/appletgui/AnnotationColourChooser.java +++ b/src/jalview/appletgui/AnnotationColourChooser.java @@ -46,7 +46,8 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.util.Hashtable; +import java.util.HashMap; +import java.util.Map; import java.util.Vector; public class AnnotationColourChooser extends Panel implements @@ -60,9 +61,15 @@ public class AnnotationColourChooser extends Panel implements ColourSchemeI oldcs; - Hashtable oldgroupColours; + Map oldgroupColours; - jalview.datamodel.AlignmentAnnotation currentAnnotation; + /* + * map from annotation to its menu item display label + * - so we know which item to pre-select on restore + */ + private Map annotationLabels; + + AlignmentAnnotation currentAnnotation; boolean adjusting = false; @@ -78,17 +85,10 @@ public class AnnotationColourChooser extends Panel implements oldcs = av.getGlobalColourScheme(); if (av.getAlignment().getGroups() != null) { - oldgroupColours = new Hashtable(); + oldgroupColours = new HashMap(); for (SequenceGroup sg : ap.av.getAlignment().getGroups()) { - if (sg.getColourScheme() != null) - { - oldgroupColours.put(sg, sg.getColourScheme()); - } - else - { - oldgroupColours.put(sg, "null"); - } + oldgroupColours.put(sg, sg.getColourScheme()); } } this.av = av; @@ -119,24 +119,7 @@ public class AnnotationColourChooser extends Panel implements // seqAssociated.setState(acg.isSeqAssociated()); } - Vector list = new Vector(); - int index = 1; - for (int i = 0; i < anns.length; i++) - { - String label = anns[i].label; - if (anns[i].sequenceRef != null) - { - label = label + "_" + anns[i].sequenceRef.getName(); - } - if (!list.contains(label)) - { - list.addElement(label); - } - else - { - list.addElement(label + "_" + (index++)); - } - } + Vector list = getAnnotationItems(); for (int i = 0; i < list.size(); i++) { @@ -153,7 +136,8 @@ public class AnnotationColourChooser extends Panel implements if (oldcs instanceof AnnotationColourGradient) { AnnotationColourGradient acg = (AnnotationColourGradient) oldcs; - annotations.select(acg.getAnnotation()); + String label = annotationLabels.get(acg.getAnnotation()); + annotations.select(label); switch (acg.getAboveThreshold()) { case AnnotationColourGradient.NO_THRESHOLD: @@ -170,7 +154,7 @@ public class AnnotationColourChooser extends Panel implements MessageManager .getString("error.implementation_error_dont_know_threshold_annotationcolourgradient")); } - thresholdIsMin.setState(acg.thresholdIsMinMax); + thresholdIsMin.setState(acg.isThresholdIsMinMax()); thresholdValue.setText("" + acg.getAnnotationThreshold()); } @@ -186,6 +170,51 @@ public class AnnotationColourChooser extends Panel implements validate(); } + /** + * Builds and returns a list of menu items (display text) for choice of + * annotation. Also builds a map between annotations and their display labels. + * + * @return + */ + protected Vector getAnnotationItems() + { + // TODO remove duplication with gui.AnnotationRowFilter + // TODO add 'per sequence only' option / parameter + + annotationLabels = new HashMap(); + Vector list = new Vector(); + AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation(); + if (anns == null) + { + return list; + } + int index = 1; + for (int i = 0; i < anns.length; i++) + { + String label = anns[i].label; + if (anns[i].sequenceRef != null) + { + /* + * be helpful and include sequence id in label for + * sequence-associated annotation (JAL-2236) + */ + label = label + "_" + anns[i].sequenceRef.getName(); + } + if (!list.contains(label)) + { + list.addElement(label); + annotationLabels.put(anns[i], label); + } + else + { + label = label + "_" + (index++); + list.addElement(label); + annotationLabels.put(anns[i], label); + } + } + return list; + } + private void setDefaultMinMax() { minColour.setBackground(av.applet.getDefaultColourParameter( @@ -501,7 +530,7 @@ public class AnnotationColourChooser extends Panel implements acg.setPredefinedColours(true); } - acg.thresholdIsMinMax = thresholdIsMin.getState(); + acg.setThresholdIsMinMax(thresholdIsMin.getState()); av.setGlobalColourScheme(acg); @@ -510,7 +539,6 @@ public class AnnotationColourChooser extends Panel implements { for (SequenceGroup sg : ap.av.getAlignment().getGroups()) { - if (sg.getColourScheme() == null) { continue; @@ -527,7 +555,6 @@ public class AnnotationColourChooser extends Panel implements currentAnnotation, minColour.getBackground(), maxColour .getBackground(), aboveThreshold)); } - } } @@ -543,20 +570,10 @@ public class AnnotationColourChooser extends Panel implements { for (SequenceGroup sg : ap.av.getAlignment().getGroups()) { - Object cs = oldgroupColours.get(sg); - if (cs instanceof ColourSchemeI) - { - sg.setColourScheme((ColourSchemeI) cs); - } - else - { - // probably the "null" string we set it to if it was null originally. - sg.setColourScheme(null); - } + sg.setColourScheme(oldgroupColours.get(sg)); } } ap.paintAlignment(true); - } @Override diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index 48c1ee9..da3cb92 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -22,6 +22,7 @@ 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; @@ -1037,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 41488ea..2dafc0c 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -1011,6 +1011,10 @@ public class Alignment implements AlignmentI } else if (dataset == null && data != null) { + if (data == this) + { + throw new IllegalArgumentException("Circular dataset reference"); + } if (!(data instanceof Alignment)) { throw new Error( diff --git a/src/jalview/datamodel/AlignmentAnnotation.java b/src/jalview/datamodel/AlignmentAnnotation.java index bbd3ce4..6117baf 100755 --- a/src/jalview/datamodel/AlignmentAnnotation.java +++ b/src/jalview/datamodel/AlignmentAnnotation.java @@ -20,10 +20,6 @@ */ package jalview.datamodel; -import jalview.analysis.Rna; -import jalview.analysis.SecStrConsensus.SimpleBP; -import jalview.analysis.WUSSParseException; - import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -32,6 +28,10 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import jalview.analysis.Rna; +import jalview.analysis.SecStrConsensus.SimpleBP; +import jalview.analysis.WUSSParseException; + /** * DOCUMENT ME! * @@ -867,6 +867,10 @@ public class AlignmentAnnotation @Override public String toString() { + if (annotations == null) + { + return ""; + } StringBuilder buffer = new StringBuilder(256); for (int i = 0; i < annotations.length; i++) diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index e37c55e..1246d23 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -1358,7 +1358,7 @@ public class SequenceGroup implements AnnotatedCollectionI AnnotatedCollectionI ref = ctx; while (ref != null) { - if (ref == this) + if (ref == this || ref.getContext() == ctx) { throw new IllegalArgumentException( "Circular reference in SequenceGroup.context"); diff --git a/src/jalview/ext/jmol/JmolCommands.java b/src/jalview/ext/jmol/JmolCommands.java index 4212749..23e0a6f 100644 --- a/src/jalview/ext/jmol/JmolCommands.java +++ b/src/jalview/ext/jmol/JmolCommands.java @@ -110,7 +110,6 @@ public class JmolCommands */ if (!cs.isVisible(r)) { - // col = ColorUtils.darkerThan(col); col = Color.GRAY; } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 1d8b944..95757fd 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -240,7 +240,6 @@ public class ChimeraCommands */ if (!cs.isVisible(r)) { - // colour = ColorUtils.darkerThan(colour); colour = Color.GRAY; } diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index ab1ac0e..507bfab 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -2750,6 +2750,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, */ newap.av.replaceMappings(viewport.getAlignment()); + /* + * start up cDNA consensus (if applicable) now mappings are in place + */ + if (newap.av.initComplementConsensus()) + { + newap.refresh(true); // adjust layout of annotations + } + newap.av.viewName = getNewViewName(viewTitle); addAlignmentPanel(newap, true); diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index ac137b9..8ade5d6 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -1884,4 +1884,26 @@ public class AlignmentPanel extends GAlignmentPanel implements { return this.dontScrollComplement; } + + /** + * Redraw sensibly. + * + * @adjustHeight if true, try to recalculate panel height for visible + * annotations + */ + protected void refresh(boolean adjustHeight) + { + validateAnnotationDimensions(adjustHeight); + addNotify(); + if (adjustHeight) + { + // sort, repaint, update overview + paintAlignment(true); + } + else + { + // lightweight repaint + repaint(); + } + } } diff --git a/src/jalview/gui/AnnotationColourChooser.java b/src/jalview/gui/AnnotationColourChooser.java index f6352d7..8500888 100644 --- a/src/jalview/gui/AnnotationColourChooser.java +++ b/src/jalview/gui/AnnotationColourChooser.java @@ -21,6 +21,8 @@ package jalview.gui; import jalview.bin.Cache; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.GraphLine; import jalview.datamodel.SequenceGroup; import jalview.schemes.AnnotationColourGradient; import jalview.schemes.ColourSchemeI; @@ -35,9 +37,11 @@ import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Hashtable; +import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JInternalFrame; @@ -49,27 +53,21 @@ import net.miginfocom.swing.MigLayout; @SuppressWarnings("serial") public class AnnotationColourChooser extends AnnotationRowFilter { + private static final int ONETHOUSAND = 1000; - ColourSchemeI oldcs; + private ColourSchemeI oldcs; - Hashtable oldgroupColours; + private JButton defColours; - /** - * enabled if the user is dragging the slider - try to keep updates to a - * minimun - */ + private Hashtable oldgroupColours; - JComboBox annotations; + private JCheckBox useOriginalColours = new JCheckBox(); - JButton defColours = new JButton(); + private JPanel minColour = new JPanel(); - JPanel jPanel1 = new JPanel(); + private JPanel maxColour = new JPanel(); - JPanel jPanel2 = new JPanel(); - - BorderLayout borderLayout1 = new BorderLayout(); - - private JComboBox threshold = new JComboBox(); + private JCheckBox thresholdIsMin = new JCheckBox(); public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap) { @@ -108,7 +106,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter if (oldcs instanceof AnnotationColourGradient) { AnnotationColourGradient acg = (AnnotationColourGradient) oldcs; - currentColours.setSelected(acg.isPredefinedColours() + useOriginalColours.setSelected(acg.isPredefinedColours() || acg.getBaseColour() != null); if (!acg.isPredefinedColours() && acg.getBaseColour() == null) { @@ -118,15 +116,17 @@ public class AnnotationColourChooser extends AnnotationRowFilter seqAssociated.setSelected(acg.isSeqAssociated()); } - annotations = new JComboBox( - getAnnotationItems(seqAssociated.isSelected())); + Vector annotItems = getAnnotationItems(seqAssociated + .isSelected()); + annotations = new JComboBox(annotItems); populateThresholdComboBox(threshold); if (oldcs instanceof AnnotationColourGradient) { AnnotationColourGradient acg = (AnnotationColourGradient) oldcs; - annotations.setSelectedItem(acg.getAnnotation()); + String label = getAnnotationMenuLabel(acg.getAnnotation()); + annotations.setSelectedItem(label); switch (acg.getAboveThreshold()) { case AnnotationColourGradient.NO_THRESHOLD: @@ -143,16 +143,11 @@ public class AnnotationColourChooser extends AnnotationRowFilter MessageManager .getString("error.implementation_error_dont_know_about_threshold_setting")); } - thresholdIsMin.setSelected(acg.thresholdIsMinMax); + thresholdIsMin.setSelected(acg.isThresholdIsMinMax()); thresholdValue.setText("" + acg.getAnnotationThreshold()); } - try - { - jbInit(); - } catch (Exception ex) - { - } + jbInit(); adjusting = false; updateView(); @@ -160,19 +155,11 @@ public class AnnotationColourChooser extends AnnotationRowFilter frame.pack(); } - public AnnotationColourChooser() + @Override + protected void jbInit() { - try - { - jbInit(); - } catch (Exception ex) - { - ex.printStackTrace(); - } - } + super.jbInit(); - private void jbInit() throws Exception - { minColour.setFont(JvSwingUtils.getLabelFont()); minColour.setBorder(BorderFactory.createEtchedBorder()); minColour.setPreferredSize(new Dimension(40, 20)); @@ -203,26 +190,8 @@ public class AnnotationColourChooser extends AnnotationRowFilter } } }); - ok.setOpaque(false); - ok.setText(MessageManager.getString("action.ok")); - ok.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - ok_actionPerformed(); - } - }); - cancel.setOpaque(false); - cancel.setText(MessageManager.getString("action.cancel")); - cancel.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - cancel_actionPerformed(); - } - }); + + defColours = new JButton(); defColours.setOpaque(false); defColours.setText(MessageManager.getString("action.set_defaults")); defColours.setToolTipText(MessageManager @@ -237,48 +206,16 @@ public class AnnotationColourChooser extends AnnotationRowFilter } }); - annotations.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - annotations_actionPerformed(); - } - }); - getThreshold().addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - threshold_actionPerformed(); - } - }); - thresholdValue.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - thresholdValue_actionPerformed(); - } - }); - slider.setPaintLabels(false); - slider.setPaintTicks(true); - slider.setBackground(Color.white); - slider.setEnabled(false); - slider.setOpaque(false); - slider.setPreferredSize(new Dimension(100, 32)); - thresholdValue.setEnabled(false); - thresholdValue.setColumns(7); - currentColours.setFont(JvSwingUtils.getLabelFont()); - currentColours.setOpaque(false); - currentColours.setText(MessageManager + useOriginalColours.setFont(JvSwingUtils.getLabelFont()); + useOriginalColours.setOpaque(false); + useOriginalColours.setText(MessageManager .getString("label.use_original_colours")); - currentColours.addActionListener(new ActionListener() + useOriginalColours.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - currentColours_actionPerformed(); + originalColours_actionPerformed(); } }); thresholdIsMin.setBackground(Color.white); @@ -307,7 +244,9 @@ public class AnnotationColourChooser extends AnnotationRowFilter } }); - this.setLayout(borderLayout1); + this.setLayout(new BorderLayout()); + JPanel jPanel1 = new JPanel(); + JPanel jPanel2 = new JPanel(); jPanel2.setLayout(new MigLayout("", "[left][center][right]", "[][][]")); jPanel1.setBackground(Color.white); jPanel2.setBackground(Color.white); @@ -316,7 +255,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter jPanel1.add(cancel); jPanel2.add(annotations, "grow, wrap"); jPanel2.add(seqAssociated); - jPanel2.add(currentColours); + jPanel2.add(useOriginalColours); JPanel colpanel = new JPanel(new FlowLayout()); colpanel.setBackground(Color.white); colpanel.add(minColour); @@ -391,7 +330,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter { if (slider.isEnabled()) { - if (currentColours.isSelected() + if (useOriginalColours.isSelected() && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient)) { updateView(); @@ -403,24 +342,16 @@ public class AnnotationColourChooser extends AnnotationRowFilter } } - public JComboBox getThreshold() + public void originalColours_actionPerformed() { - return threshold; - } - - public void setThreshold(JComboBox threshold) - { - this.threshold = threshold; - } - - public void currentColours_actionPerformed() - { - if (currentColours.isSelected()) + boolean selected = useOriginalColours.isSelected(); + if (selected) { reset(); } - maxColour.setEnabled(!currentColours.isSelected()); - minColour.setEnabled(!currentColours.isSelected()); + maxColour.setEnabled(!selected); + minColour.setEnabled(!selected); + thresholdIsMin.setEnabled(!selected); updateView(); } @@ -441,7 +372,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter slider.setEnabled(true); thresholdValue.setEnabled(true); - thresholdIsMin.setEnabled(true); + thresholdIsMin.setEnabled(!useOriginalColours.isSelected()); if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD) { @@ -455,7 +386,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter { getCurrentAnnotation() .setThreshold( - new jalview.datamodel.GraphLine( + new GraphLine( (getCurrentAnnotation().graphMax - getCurrentAnnotation().graphMin) / 2f, "Threshold", Color.black)); } @@ -463,19 +394,19 @@ public class AnnotationColourChooser extends AnnotationRowFilter if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD) { adjusting = true; - float range = getCurrentAnnotation().graphMax * 1000 - - getCurrentAnnotation().graphMin * 1000; + float range = getCurrentAnnotation().graphMax * ONETHOUSAND + - getCurrentAnnotation().graphMin * ONETHOUSAND; - slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000)); - slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000)); - slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000)); + slider.setMinimum((int) (getCurrentAnnotation().graphMin * ONETHOUSAND)); + slider.setMaximum((int) (getCurrentAnnotation().graphMax * ONETHOUSAND)); + slider.setValue((int) (getCurrentAnnotation().threshold.value * ONETHOUSAND)); thresholdValue.setText(getCurrentAnnotation().threshold.value + ""); slider.setMajorTickSpacing((int) (range / 10f)); slider.setEnabled(true); thresholdValue.setEnabled(true); adjusting = false; } - colorAlignmContaining(getCurrentAnnotation(), selectedThresholdItem); + colorAlignmentContaining(getCurrentAnnotation(), selectedThresholdItem); ap.alignmentChanged(); // ensure all associated views (overviews, structures, etc) are notified of @@ -483,4 +414,60 @@ public class AnnotationColourChooser extends AnnotationRowFilter ap.paintAlignment(true); } + protected boolean colorAlignmentContaining(AlignmentAnnotation currentAnn, int selectedThresholdOption) + { + + AnnotationColourGradient acg = null; + if (useOriginalColours.isSelected()) + { + acg = new AnnotationColourGradient(currentAnn, + av.getGlobalColourScheme(), selectedThresholdOption); + } + else + { + acg = new AnnotationColourGradient(currentAnn, + minColour.getBackground(), maxColour.getBackground(), + selectedThresholdOption); + } + acg.setSeqAssociated(seqAssociated.isSelected()); + + if (currentAnn.graphMin == 0f && currentAnn.graphMax == 0f) + { + acg.setPredefinedColours(true); + } + + acg.setThresholdIsMinMax(thresholdIsMin.isSelected()); + + av.setGlobalColourScheme(acg); + + if (av.getAlignment().getGroups() != null) + { + + for (SequenceGroup sg : ap.av.getAlignment().getGroups()) + { + if (sg.cs == null) + { + continue; + } + + if (useOriginalColours.isSelected()) + { + sg.setColourScheme(new AnnotationColourGradient(currentAnn, sg + .getColourScheme(), selectedThresholdOption)); + ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated + .isSelected()); + } + else + { + sg.setColourScheme(new AnnotationColourGradient(currentAnn, + minColour.getBackground(), maxColour.getBackground(), + selectedThresholdOption)); + ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated + .isSelected()); + } + } + } + return false; + } + } diff --git a/src/jalview/gui/AnnotationColumnChooser.java b/src/jalview/gui/AnnotationColumnChooser.java index 1290d70..637eb30 100644 --- a/src/jalview/gui/AnnotationColumnChooser.java +++ b/src/jalview/gui/AnnotationColumnChooser.java @@ -30,7 +30,6 @@ import jalview.viewmodel.annotationfilter.AnnotationFilterParameter; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; -import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; @@ -55,28 +54,10 @@ import net.miginfocom.swing.MigLayout; public class AnnotationColumnChooser extends AnnotationRowFilter implements ItemListener { - - private JComboBox annotations; - - private JPanel actionPanel = new JPanel(); - - private JPanel thresholdPanel = new JPanel(); - private JPanel switchableViewsPanel = new JPanel(new CardLayout()); - private CardLayout switchableViewsLayout = (CardLayout) (switchableViewsPanel - .getLayout()); - - private JPanel noGraphFilterView = new JPanel(); - - private JPanel graphFilterView = new JPanel(); - private JPanel annotationComboBoxPanel = new JPanel(); - private BorderLayout borderLayout1 = new BorderLayout(); - - private JComboBox threshold = new JComboBox(); - private StructureFilterPanel gStructureFilterPanel; private StructureFilterPanel ngStructureFilterPanel; @@ -107,17 +88,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements private ColumnSelection oldColumnSelection; - public AnnotationColumnChooser() - { - try - { - jbInit(); - } catch (Exception ex) - { - ex.printStackTrace(); - } - } - public AnnotationColumnChooser(AlignViewport av, final AlignmentPanel ap) { super(av, ap); @@ -169,72 +139,27 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements frame.pack(); } - private void jbInit() throws Exception + @Override + protected void jbInit() { - ok.setOpaque(false); - ok.setText(MessageManager.getString("action.ok")); - ok.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - ok_actionPerformed(); - } - }); - - cancel.setOpaque(false); - cancel.setText(MessageManager.getString("action.cancel")); - cancel.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - cancel_actionPerformed(); - } - }); - - annotations.addItemListener(this); - annotations.setToolTipText(MessageManager - .getString("info.select_annotation_row")); - threshold.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - threshold_actionPerformed(); - } - }); - - thresholdValue.setEnabled(false); - thresholdValue.setColumns(7); - thresholdValue.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - thresholdValue_actionPerformed(); - } - }); - - slider.setPaintLabels(false); - slider.setPaintTicks(true); - slider.setBackground(Color.white); - slider.setEnabled(false); - slider.setOpaque(false); - slider.setPreferredSize(new Dimension(100, 32)); + super.jbInit(); + JPanel thresholdPanel = new JPanel(); thresholdPanel.setBorder(new TitledBorder(MessageManager .getString("label.threshold_filter"))); thresholdPanel.setBackground(Color.white); thresholdPanel.setFont(JvSwingUtils.getLabelFont()); thresholdPanel.setLayout(new MigLayout("", "[left][right]", "[][]")); + JPanel actionPanel = new JPanel(); actionPanel.setBackground(Color.white); actionPanel.setFont(JvSwingUtils.getLabelFont()); + JPanel graphFilterView = new JPanel(); graphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]")); graphFilterView.setBackground(Color.white); + JPanel noGraphFilterView = new JPanel(); noGraphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]")); noGraphFilterView.setBackground(Color.white); @@ -270,7 +195,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements switchableViewsPanel.add(graphFilterView, AnnotationColumnChooser.GRAPH_VIEW); - this.setLayout(borderLayout1); + this.setLayout(new BorderLayout()); this.add(annotationComboBoxPanel, java.awt.BorderLayout.PAGE_START); this.add(switchableViewsPanel, java.awt.BorderLayout.CENTER); this.add(actionPanel, java.awt.BorderLayout.SOUTH); @@ -280,7 +205,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements this.validate(); } - public void updateThresholdPanelToolTip() + protected void updateThresholdPanelToolTip() { thresholdValue.setToolTipText(""); slider.setToolTipText(""); @@ -297,7 +222,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements } @Override - public void reset() + protected void reset() { if (this.getOldColumnSelection() != null) { @@ -338,26 +263,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements } } - public JComboBox getThreshold() - { - return threshold; - } - - public void setThreshold(JComboBox threshold) - { - this.threshold = threshold; - } - - public JComboBox getAnnotations() - { - return annotations; - } - - public void setAnnotations(JComboBox annotations) - { - this.annotations = annotations; - } - @Override public void updateView() { @@ -568,6 +473,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements selectedAnnotationChanged(); } + @Override public void selectedAnnotationChanged() { String currentView = AnnotationColumnChooser.NO_GRAPH_VIEW; @@ -585,6 +491,8 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements ngFurtherActionPanel.syncState(); ngStructureFilterPanel.syncState(); + CardLayout switchableViewsLayout = (CardLayout) switchableViewsPanel + .getLayout(); switchableViewsLayout.show(switchableViewsPanel, currentView); updateView(); } diff --git a/src/jalview/gui/AnnotationLabels.java b/src/jalview/gui/AnnotationLabels.java index 4b774d6..c9535d0 100755 --- a/src/jalview/gui/AnnotationLabels.java +++ b/src/jalview/gui/AnnotationLabels.java @@ -292,33 +292,11 @@ public class AnnotationLabels extends JPanel implements MouseListener, aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel; } - refresh(fullRepaint); + ap.refresh(fullRepaint); } /** - * Redraw sensibly. - * - * @adjustHeight if true, try to recalculate panel height for visible - * annotations - */ - protected void refresh(boolean adjustHeight) - { - ap.validateAnnotationDimensions(adjustHeight); - ap.addNotify(); - if (adjustHeight) - { - // sort, repaint, update overview - ap.paintAlignment(true); - } - else - { - // lightweight repaint - ap.repaint(); - } - } - - /** * DOCUMENT ME! * * @param e @@ -420,7 +398,7 @@ public class AnnotationLabels extends JPanel implements MouseListener, // ann.visible = false; // } // } - refresh(true); + ap.refresh(true); } }); pop.add(hideType); diff --git a/src/jalview/gui/AnnotationRowFilter.java b/src/jalview/gui/AnnotationRowFilter.java index 7705bc3..c2bf41b 100644 --- a/src/jalview/gui/AnnotationRowFilter.java +++ b/src/jalview/gui/AnnotationRowFilter.java @@ -22,14 +22,21 @@ package jalview.gui; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.GraphLine; -import jalview.datamodel.SequenceGroup; import jalview.schemes.AnnotationColourGradient; import jalview.util.MessageManager; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Map; import java.util.Vector; import javax.swing.JButton; @@ -51,22 +58,10 @@ public abstract class AnnotationRowFilter extends JPanel protected int[] annmap; - protected boolean enableSeqAss = false; - - private AlignmentAnnotation currentAnnotation; - protected boolean adjusting = false; - protected JCheckBox currentColours = new JCheckBox(); - - protected JPanel minColour = new JPanel(); - - protected JPanel maxColour = new JPanel(); - protected JCheckBox seqAssociated = new JCheckBox(); - protected JCheckBox thresholdIsMin = new JCheckBox(); - protected JSlider slider = new JSlider(); protected JTextField thresholdValue = new JTextField(20); @@ -83,6 +78,38 @@ public abstract class AnnotationRowFilter extends JPanel */ protected boolean sliderDragging = false; + protected JComboBox threshold = new JComboBox(); + + protected JComboBox annotations; + + /* + * map from annotation to its menu item display label + * - so we know which item to pre-select on restore + */ + private Map annotationLabels; + + private AlignmentAnnotation currentAnnotation; + + /** + * Constructor + * + * @param viewport + * @param alignPanel + */ + public AnnotationRowFilter(AlignViewport viewport, final AlignmentPanel alignPanel) + { + this.av = viewport; + this.ap = alignPanel; + thresholdValue.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + thresholdValue_actionPerformed(); + } + }); + } + protected void addSliderChangeListener() { @@ -132,33 +159,27 @@ public abstract class AnnotationRowFilter extends JPanel }); } - public AnnotationRowFilter(AlignViewport av, final AlignmentPanel ap) - { - this.av = av; - this.ap = ap; - thresholdValue.addFocusListener(new FocusAdapter() - { - @Override - public void focusLost(FocusEvent e) - { - thresholdValue_actionPerformed(); - } - }); - } - - public AnnotationRowFilter() - { - - } - +/** + * Builds and returns a list of menu items (display text) for choice of + * annotation. Also builds maps between annotations, their positions in the + * list, and their display labels in the list. + * + * @param isSeqAssociated + * @return + */ public Vector getAnnotationItems(boolean isSeqAssociated) { + annotationLabels = new HashMap(); + Vector list = new Vector(); int index = 1; int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length]; + seqAssociated.setEnabled(false); for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++) { - if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null) + AlignmentAnnotation annotation = av.getAlignment() + .getAlignmentAnnotation()[i]; + if (annotation.sequenceRef == null) { if (isSeqAssociated) { @@ -167,30 +188,29 @@ public abstract class AnnotationRowFilter extends JPanel } else { - enableSeqAss = true; + seqAssociated.setEnabled(true); } - String label = av.getAlignment().getAlignmentAnnotation()[i].label; + String label = annotation.label; // add associated sequence ID if available - if (!isSeqAssociated - && av.getAlignment().getAlignmentAnnotation()[i].sequenceRef != null) + if (!isSeqAssociated && annotation.sequenceRef != null) { - label = label - + "_" - + av.getAlignment().getAlignmentAnnotation()[i].sequenceRef - .getName(); + label = label + "_" + annotation.sequenceRef.getName(); } // make label unique if (!list.contains(label)) { anmap[list.size()] = i; list.add(label); + annotationLabels.put(annotation, label); } else { if (!isSeqAssociated) { anmap[list.size()] = i; - list.add(label + "_" + (index++)); + label = label + "_" + (index++); + list.add(label); + annotationLabels.put(annotation, label); } } } @@ -213,11 +233,6 @@ public abstract class AnnotationRowFilter extends JPanel return selectedThresholdItem; } - public void modelChanged() - { - seqAssociated.setEnabled(enableSeqAss); - } - public void ok_actionPerformed() { try @@ -240,22 +255,22 @@ public abstract class AnnotationRowFilter extends JPanel } } - public void thresholdCheck_actionPerformed() + protected void thresholdCheck_actionPerformed() { updateView(); } - public void annotations_actionPerformed() + protected void selectedAnnotationChanged() { updateView(); } - public void threshold_actionPerformed() + protected void threshold_actionPerformed() { updateView(); } - public void thresholdValue_actionPerformed() + protected void thresholdValue_actionPerformed() { try { @@ -267,27 +282,34 @@ public abstract class AnnotationRowFilter extends JPanel } } - public void thresholdIsMin_actionPerformed() + protected void thresholdIsMin_actionPerformed() { updateView(); } - protected void populateThresholdComboBox(JComboBox threshold) + protected void populateThresholdComboBox(JComboBox thresh) { - threshold.addItem(MessageManager + thresh.addItem(MessageManager .getString("label.threshold_feature_no_threshold")); - threshold.addItem(MessageManager + thresh.addItem(MessageManager .getString("label.threshold_feature_above_threshold")); - threshold.addItem(MessageManager + thresh.addItem(MessageManager .getString("label.threshold_feature_below_threshold")); } - protected void seqAssociated_actionPerformed(JComboBox annotations) + /** + * Rebuilds the drop-down list of annotations to choose from when the 'per + * sequence only' checkbox is checked or unchecked. + * + * @param anns + */ + protected void seqAssociated_actionPerformed(JComboBox anns) { adjusting = true; - String cursel = (String) annotations.getSelectedItem(); - boolean isvalid = false, isseqs = seqAssociated.isSelected(); - annotations.removeAllItems(); + String cursel = (String) anns.getSelectedItem(); + boolean isvalid = false; + boolean isseqs = seqAssociated.isSelected(); + anns.removeAllItems(); for (String anitem : getAnnotationItems(seqAssociated.isSelected())) { if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem))) @@ -295,20 +317,22 @@ public abstract class AnnotationRowFilter extends JPanel isvalid = true; cursel = anitem; } - annotations.addItem(anitem); + anns.addItem(anitem); } - adjusting = false; if (isvalid) { - annotations.setSelectedItem(cursel); + anns.setSelectedItem(cursel); } else { - if (annotations.getItemCount() > 0) + if (anns.getItemCount() > 0) { - annotations.setSelectedIndex(0); + anns.setSelectedIndex(0); } } + adjusting = false; + + updateView(); } protected void propagateSeqAssociatedThreshold(boolean allAnnotation, @@ -339,76 +363,107 @@ public abstract class AnnotationRowFilter extends JPanel } } - protected boolean colorAlignmContaining(AlignmentAnnotation currentAnn, - int selectedThresholdOption) + public AlignmentAnnotation getCurrentAnnotation() { + return currentAnnotation; + } - AnnotationColourGradient acg = null; - if (currentColours.isSelected()) - { - acg = new AnnotationColourGradient(currentAnn, - av.getGlobalColourScheme(), selectedThresholdOption); - } - else - { - acg = new AnnotationColourGradient(currentAnn, - minColour.getBackground(), maxColour.getBackground(), - selectedThresholdOption); - } - acg.setSeqAssociated(seqAssociated.isSelected()); + protected void setCurrentAnnotation(AlignmentAnnotation annotation) + { + this.currentAnnotation = annotation; + } - if (currentAnn.graphMin == 0f && currentAnn.graphMax == 0f) - { - acg.setPredefinedColours(true); - } + protected abstract void valueChanged(boolean updateAllAnnotation); - acg.thresholdIsMinMax = thresholdIsMin.isSelected(); + protected abstract void updateView(); - av.setGlobalColourScheme(acg); + protected abstract void reset(); - if (av.getAlignment().getGroups() != null) + protected String getAnnotationMenuLabel(AlignmentAnnotation ann) + { + return annotationLabels.get(ann); + } + + protected void jbInit() + { + ok.setOpaque(false); + ok.setText(MessageManager.getString("action.ok")); + ok.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) + { + ok_actionPerformed(); + } + }); - for (SequenceGroup sg : ap.av.getAlignment().getGroups()) + cancel.setOpaque(false); + cancel.setText(MessageManager.getString("action.cancel")); + cancel.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) { - if (sg.cs == null) - { - continue; - } + cancel_actionPerformed(); + } + }); - AnnotationColourGradient scheme = null; - if (currentColours.isSelected()) - { - scheme = new AnnotationColourGradient(currentAnn, - sg.getColourScheme(), selectedThresholdOption); - } - else - { - scheme = new AnnotationColourGradient(currentAnn, - minColour.getBackground(), maxColour.getBackground(), - selectedThresholdOption); - } - scheme.setSeqAssociated(seqAssociated.isSelected()); - sg.setColourScheme(scheme); + annotations.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + selectedAnnotationChanged(); } - } - return false; + }); + annotations.setToolTipText(MessageManager + .getString("info.select_annotation_row")); + + threshold.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + threshold_actionPerformed(); + } + }); + + thresholdValue.setEnabled(false); + thresholdValue.setColumns(7); + thresholdValue.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + thresholdValue_actionPerformed(); + } + }); + + slider.setPaintLabels(false); + slider.setPaintTicks(true); + slider.setBackground(Color.white); + slider.setEnabled(false); + slider.setOpaque(false); + slider.setPreferredSize(new Dimension(100, 32)); } - public jalview.datamodel.AlignmentAnnotation getCurrentAnnotation() + public JComboBox getThreshold() { - return currentAnnotation; + return threshold; } - public void setCurrentAnnotation( - jalview.datamodel.AlignmentAnnotation currentAnnotation) + public void setThreshold(JComboBox thresh) { - this.currentAnnotation = currentAnnotation; + this.threshold = thresh; } - public abstract void valueChanged(boolean updateAllAnnotation); - - public abstract void updateView(); + public JComboBox getAnnotations() + { + return annotations; + } - public abstract void reset(); + public void setAnnotations(JComboBox anns) + { + this.annotations = anns; + } } 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/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index e8b832d..c19f005 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -29,6 +29,7 @@ import jalview.datamodel.AlignedCodonFrame; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; +import jalview.datamodel.GraphLine; import jalview.datamodel.PDBEntry; import jalview.datamodel.RnaViewerModel; import jalview.datamodel.SequenceGroup; @@ -77,7 +78,6 @@ import jalview.schemes.AnnotationColourGradient; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemeProperty; import jalview.schemes.FeatureColour; -import jalview.schemes.ResidueColourScheme; import jalview.schemes.ResidueProperties; import jalview.schemes.UserColourScheme; import jalview.structure.StructureSelectionManager; @@ -1713,6 +1713,15 @@ public class Jalview2XML return matchedFile; } + /** + * Populates the AnnotationColours xml for save. This captures the settings of + * the options in the 'Colour by Annotation' dialog. + * + * @param acg + * @param userColours + * @param jms + * @return + */ private AnnotationColours constructAnnotationColours( AnnotationColourGradient acg, List userColours, JalviewModelSequence jms) @@ -1720,8 +1729,9 @@ public class Jalview2XML AnnotationColours ac = new AnnotationColours(); ac.setAboveThreshold(acg.getAboveThreshold()); ac.setThreshold(acg.getAnnotationThreshold()); - ac.setAnnotation(acg.getAnnotation()); - if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme) + // 2.10.2 save annotationId (unique) not annotation label + ac.setAnnotation(acg.getAnnotation().annotationId); + if (acg.getBaseColour() instanceof UserColourScheme) { ac.setColourScheme(setUserColourScheme(acg.getBaseColour(), userColours, jms)); @@ -2641,10 +2651,12 @@ public class Jalview2XML @Override public void run() { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, - finalErrorMessage, "Error " - + (saving ? "saving" : "loading") - + " Jalview file", JvOptionPane.WARNING_MESSAGE); + JvOptionPane + .showInternalMessageDialog(Desktop.desktop, + finalErrorMessage, "Error " + + (saving ? "saving" : "loading") + + " Jalview file", + JvOptionPane.WARNING_MESSAGE); } }); } @@ -4691,12 +4703,21 @@ public class Jalview2XML return af; } + /** + * Reads saved data to restore Colour by Annotation settings + * + * @param viewAnnColour + * @param af + * @param al + * @param jms + * @param checkGroupAnnColour + * @return + */ private ColourSchemeI constructAnnotationColour( AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al, JalviewModelSequence jms, boolean checkGroupAnnColour) { boolean propagateAnnColour = false; - ColourSchemeI cs = null; AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al; if (checkGroupAnnColour && al.getGroups() != null && al.getGroups().size() > 0) @@ -4704,7 +4725,7 @@ public class Jalview2XML // pre 2.8.1 behaviour // check to see if we should transfer annotation colours propagateAnnColour = true; - for (jalview.datamodel.SequenceGroup sg : al.getGroups()) + for (SequenceGroup sg : al.getGroups()) { if (sg.getColourScheme() instanceof AnnotationColourGradient) { @@ -4712,107 +4733,84 @@ public class Jalview2XML } } } - // int find annotation - if (annAlignment.getAlignmentAnnotation() != null) + + /* + * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId + */ + String annotationId = viewAnnColour.getAnnotation(); + AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId); + + /* + * pre 2.10.2: saved annotationId is AlignmentAnnotation.label + */ + if (matchedAnnotation == null && annAlignment.getAlignmentAnnotation() != null) { for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++) { - if (annAlignment.getAlignmentAnnotation()[i].label - .equals(viewAnnColour.getAnnotation())) + if (annotationId + .equals(annAlignment.getAlignmentAnnotation()[i].label)) { - if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null) - { - annAlignment.getAlignmentAnnotation()[i] - .setThreshold(new jalview.datamodel.GraphLine( - viewAnnColour.getThreshold(), "Threshold", - java.awt.Color.black) - - ); - } - - if (viewAnnColour.getColourScheme().equals( - ResidueColourScheme.NONE)) - { - cs = new AnnotationColourGradient( - annAlignment.getAlignmentAnnotation()[i], - new java.awt.Color(viewAnnColour.getMinColour()), - new java.awt.Color(viewAnnColour.getMaxColour()), - viewAnnColour.getAboveThreshold()); - } - else if (viewAnnColour.getColourScheme().startsWith("ucs")) - { - cs = new AnnotationColourGradient( - annAlignment.getAlignmentAnnotation()[i], - getUserColourScheme(jms, - viewAnnColour.getColourScheme()), - viewAnnColour.getAboveThreshold()); - } - else - { - cs = new AnnotationColourGradient( - annAlignment.getAlignmentAnnotation()[i], - ColourSchemeProperty.getColourScheme(al, - viewAnnColour.getColourScheme()), - viewAnnColour.getAboveThreshold()); - } - if (viewAnnColour.hasPerSequence()) - { - ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour - .isPerSequence()); - } - if (viewAnnColour.hasPredefinedColours()) - { - ((AnnotationColourGradient) cs) - .setPredefinedColours(viewAnnColour - .isPredefinedColours()); - } - if (propagateAnnColour && al.getGroups() != null) - { - // Also use these settings for all the groups - for (int g = 0; g < al.getGroups().size(); g++) - { - jalview.datamodel.SequenceGroup sg = al.getGroups().get(g); - - if (sg.cs == null) - { - continue; - } + matchedAnnotation = annAlignment.getAlignmentAnnotation()[i]; + break; + } + } + } + if (matchedAnnotation == null) + { + System.err.println("Failed to match annotation colour scheme for " + + annotationId); + return null; + } + if (matchedAnnotation.getThreshold() == null) + { + matchedAnnotation.setThreshold(new GraphLine(viewAnnColour.getThreshold(), + "Threshold", Color.black)); + } - /* - * if (viewAnnColour.getColourScheme().equals(ResidueColourScheme.NONE)) { sg.cs = - * new AnnotationColourGradient( - * annAlignment.getAlignmentAnnotation()[i], new - * java.awt.Color(viewAnnColour. getMinColour()), new - * java.awt.Color(viewAnnColour. getMaxColour()), - * viewAnnColour.getAboveThreshold()); } else - */ - { - sg.setColourScheme(new AnnotationColourGradient( - annAlignment.getAlignmentAnnotation()[i], sg - .getColourScheme(), viewAnnColour - .getAboveThreshold())); - if (cs instanceof AnnotationColourGradient) - { - if (viewAnnColour.hasPerSequence()) - { - ((AnnotationColourGradient) cs) - .setSeqAssociated(viewAnnColour.isPerSequence()); - } - if (viewAnnColour.hasPredefinedColours()) - { - ((AnnotationColourGradient) cs) - .setPredefinedColours(viewAnnColour - .isPredefinedColours()); - } - } - } + AnnotationColourGradient cs = null; + if (viewAnnColour.getColourScheme().equals("None")) + { + cs = new AnnotationColourGradient(matchedAnnotation, new Color( + viewAnnColour.getMinColour()), new Color( + viewAnnColour.getMaxColour()), + viewAnnColour.getAboveThreshold()); + } + else if (viewAnnColour.getColourScheme().startsWith("ucs")) + { + cs = new AnnotationColourGradient(matchedAnnotation, getUserColourScheme( + jms, viewAnnColour.getColourScheme()), + viewAnnColour.getAboveThreshold()); + } + else + { + cs = new AnnotationColourGradient(matchedAnnotation, + ColourSchemeProperty.getColourScheme(al, + viewAnnColour.getColourScheme()), + viewAnnColour.getAboveThreshold()); + } - } - } + boolean perSequenceOnly = viewAnnColour.isPerSequence(); + boolean useOriginalColours = viewAnnColour.isPredefinedColours(); + cs.setSeqAssociated(perSequenceOnly); + cs.setPredefinedColours(useOriginalColours); - break; + if (propagateAnnColour && al.getGroups() != null) + { + // Also use these settings for all the groups + for (int g = 0; g < al.getGroups().size(); g++) + { + SequenceGroup sg = al.getGroups().get(g); + if (sg.getGroupColourScheme() == null) + { + continue; } + AnnotationColourGradient groupScheme = new AnnotationColourGradient( + matchedAnnotation, sg.getColourScheme(), + viewAnnColour.getAboveThreshold()); + sg.setColourScheme(groupScheme); + groupScheme.setSeqAssociated(perSequenceOnly); + groupScheme.setPredefinedColours(useOriginalColours); } } return cs; diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 81c3d4f..d91fa70 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -43,7 +43,6 @@ import jalview.io.FileFormatI; import jalview.io.FileFormats; import jalview.io.FormatAdapter; import jalview.io.SequenceAnnotationReport; -import jalview.schemes.AnnotationColourGradient; import jalview.schemes.Blosum62ColourScheme; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemes; @@ -1179,12 +1178,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener hideInsertions_actionPerformed(e); } }); - /* - * annotationMenuItem.setText("By Annotation"); - * annotationMenuItem.addActionListener(new ActionListener() { public void - * actionPerformed(ActionEvent actionEvent) { - * annotationMenuItem_actionPerformed(actionEvent); } }); - */ + groupMenu.add(sequenceSelDetails); add(groupMenu); add(sequenceMenu); @@ -1620,24 +1614,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener refresh(); } - public void annotationMenuItem_actionPerformed(ActionEvent actionEvent) - { - SequenceGroup sg = getGroup(); - if (sg == null) - { - return; - } - - AnnotationColourGradient acg = new AnnotationColourGradient( - sequence.getAnnotation()[0], null, - AnnotationColourGradient.NO_THRESHOLD); - - acg.setPredefinedColours(true); - sg.setColourScheme(acg); - - refresh(); - } - /** * DOCUMENT ME! * diff --git a/src/jalview/gui/TextColourChooser.java b/src/jalview/gui/TextColourChooser.java index 49fdaf7..91e05c6 100644 --- a/src/jalview/gui/TextColourChooser.java +++ b/src/jalview/gui/TextColourChooser.java @@ -28,11 +28,12 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JColorChooser; import javax.swing.JLabel; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.event.ChangeEvent; @@ -44,36 +45,43 @@ public class TextColourChooser SequenceGroup sg; - public void chooseColour(AlignmentPanel ap, SequenceGroup sg) + Color original1, original2; + + int originalThreshold; + + Map groupColour1; + + Map groupColour2; + + Map groupThreshold; + + /** + * Show a dialogue which allows the user to select two text colours and adjust + * a slider for the cross-over point + * + * @param alignPanel + * the AlignmentPanel context + * @param sequenceGroup + * the SequenceGroup context (only for group pop-menu option) + */ + public void chooseColour(AlignmentPanel alignPanel, SequenceGroup sequenceGroup) { - this.ap = ap; - this.sg = sg; + this.ap = alignPanel; + this.sg = sequenceGroup; - int original1, original2, originalThreshold; - if (sg == null) - { - original1 = ap.av.getTextColour().getRGB(); - original2 = ap.av.getTextColour2().getRGB(); - originalThreshold = ap.av.getThresholdTextColour(); - } - else - { - original1 = sg.textColour.getRGB(); - original2 = sg.textColour2.getRGB(); - originalThreshold = sg.thresholdTextColour; - } + saveInitialSettings(); final JSlider slider = new JSlider(0, 750, originalThreshold); final JPanel col1 = new JPanel(); col1.setPreferredSize(new Dimension(40, 20)); col1.setBorder(BorderFactory.createEtchedBorder()); col1.setToolTipText(MessageManager.getString("label.dark_colour")); - col1.setBackground(new Color(original1)); + col1.setBackground(original1); final JPanel col2 = new JPanel(); col2.setPreferredSize(new Dimension(40, 20)); col2.setBorder(BorderFactory.createEtchedBorder()); col2.setToolTipText(MessageManager.getString("label.light_colour")); - col2.setBackground(new Color(original2)); + col2.setBackground(original2); final JPanel bigpanel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(); bigpanel.add(panel, BorderLayout.CENTER); @@ -130,7 +138,7 @@ public class TextColourChooser int reply = JvOptionPane .showInternalOptionDialog( - ap, + alignPanel, bigpanel, MessageManager .getString("label.adjunst_foreground_text_colour_threshold"), @@ -139,19 +147,81 @@ public class TextColourChooser if (reply == JvOptionPane.CANCEL_OPTION) { - if (sg == null) - { - ap.av.setTextColour(new Color(original1)); - ap.av.setTextColour2(new Color(original2)); - ap.av.setThresholdTextColour(originalThreshold); - } - else + restoreInitialSettings(); + } + } + + /** + * Restore initial settings on Cancel + */ + protected void restoreInitialSettings() + { + if (sg == null) + { + ap.av.setTextColour(original1); + ap.av.setTextColour2(original2); + ap.av.setThresholdTextColour(originalThreshold); + } + else + { + sg.textColour = original1; + sg.textColour2 = original2; + sg.thresholdTextColour = originalThreshold; + } + + /* + * if 'Apply To All Groups' was in force, there will be + * group-specific settings to restore as well + */ + for (SequenceGroup group : this.groupColour1.keySet()) + { + group.textColour = groupColour1.get(group); + group.textColour2 = groupColour2.get(group); + group.thresholdTextColour = groupThreshold.get(group); + } + } + + /** + * Save settings on entry, for restore on Cancel + */ + protected void saveInitialSettings() + { + groupColour1 = new HashMap(); + groupColour2 = new HashMap(); + groupThreshold = new HashMap(); + + if (sg == null) + { + /* + * alignment scope + */ + original1 = ap.av.getTextColour(); + original2 = ap.av.getTextColour2(); + originalThreshold = ap.av.getThresholdTextColour(); + if (ap.av.getColourAppliesToAllGroups() + && ap.av.getAlignment().getGroups() != null) { - sg.textColour = new Color(original1); - sg.textColour2 = new Color(original2); - sg.thresholdTextColour = originalThreshold; + /* + * if applying changes to all groups, need to be able to + * restore group settings as well + */ + for (SequenceGroup group : ap.av.getAlignment().getGroups()) + { + groupColour1.put(group, group.textColour); + groupColour2.put(group, group.textColour2); + groupThreshold.put(group, group.thresholdTextColour); + } } } + else + { + /* + * Sequence group scope + */ + original1 = sg.textColour; + original2 = sg.textColour2; + originalThreshold = sg.thresholdTextColour; + } } void colour1Changed(Color col) @@ -215,11 +285,11 @@ public class TextColourChooser return; } - for (SequenceGroup sg : ap.av.getAlignment().getGroups()) + for (SequenceGroup group : ap.av.getAlignment().getGroups()) { - sg.textColour = ap.av.getTextColour(); - sg.textColour2 = ap.av.getTextColour2(); - sg.thresholdTextColour = ap.av.getThresholdTextColour(); + group.textColour = ap.av.getTextColour(); + group.textColour2 = ap.av.getTextColour2(); + group.thresholdTextColour = ap.av.getThresholdTextColour(); } } diff --git a/src/jalview/gui/UserDefinedColours.java b/src/jalview/gui/UserDefinedColours.java index 83a8d24..9ab4327 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; @@ -71,7 +72,7 @@ public class UserDefinedColours extends GUserDefinedColours implements private static final String LAST_DIRECTORY = "LAST_DIRECTORY"; - private static final int MY_FRAME_HEIGHT = 420; + private static final int MY_FRAME_HEIGHT = 440; private static final int MY_FRAME_WIDTH = 810; @@ -276,14 +277,9 @@ public class UserDefinedColours extends GUserDefinedColours implements { JButton button = null; final Color newColour = colorChooser.getColor(); - for (int i = 0; i < selectedButtons.size(); i++) - { - button = selectedButtons.get(i); - button.setBackground(newColour); - button.setForeground(ColorUtils.brighterThan(newColour)); - } if (lcaseColour.isSelected()) { + selectedButtons.clear(); for (int i = 0; i < lowerCaseButtons.size(); i++) { button = lowerCaseButtons.get(i); @@ -291,6 +287,12 @@ public class UserDefinedColours extends GUserDefinedColours implements button.setForeground(ColorUtils.brighterThan(button.getBackground())); } } + for (int i = 0; i < selectedButtons.size(); i++) + { + button = selectedButtons.get(i); + button.setBackground(newColour); + button.setForeground(ColorUtils.brighterThan(newColour)); + } } /** @@ -589,11 +591,6 @@ public class UserDefinedColours extends GUserDefinedColours implements ucs.setLowerCaseColours(newColours); } - // if (ap != null) - // { - // ucs.setThreshold(0, ap.av.isIgnoreGapsConsensus()); - // } - return ucs; } @@ -625,7 +622,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 +671,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/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index b39f4a8..b759d64 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -113,7 +113,7 @@ public class GAlignFrame extends JInternalFrame protected JMenu colourMenu = new JMenu(); - protected JRadioButtonMenuItem textColour; + protected JMenuItem textColour; protected JCheckBoxMenuItem conservationMenuItem; @@ -975,7 +975,7 @@ public class GAlignFrame extends JInternalFrame }); JMenuItem createGroup = new JMenuItem( - MessageManager.getString("action.create_groups")); + MessageManager.getString("action.create_group")); keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, Toolkit .getDefaultToolkit().getMenuShortcutKeyMask(), false); al = new ActionListener() @@ -1653,6 +1653,7 @@ public class GAlignFrame extends JInternalFrame formatMenu.setText(MessageManager.getString("action.format")); JMenu selectMenu = new JMenu(MessageManager.getString("action.select")); + idRightAlign.setText(MessageManager .getString("label.right_align_sequence_id")); idRightAlign.addActionListener(new ActionListener() @@ -1904,6 +1905,12 @@ public class GAlignFrame extends JInternalFrame // selectMenu.add(listenToViewSelections); } + protected void configureSelectMenu() + { + // TODO Auto-generated method stub + + } + /** * Constructs the entries on the Colour menu (but does not add them to the * menu). @@ -1921,8 +1928,8 @@ public class GAlignFrame extends JInternalFrame } }); - textColour = new JRadioButtonMenuItem( - MessageManager.getString("action.set_text_colour")); + textColour = new JMenuItem( + MessageManager.getString("label.text_colour")); textColour.addActionListener(new ActionListener() { @Override diff --git a/src/jalview/schemes/AnnotationColourGradient.java b/src/jalview/schemes/AnnotationColourGradient.java index 1a3e9ef..220d3ab 100755 --- a/src/jalview/schemes/AnnotationColourGradient.java +++ b/src/jalview/schemes/AnnotationColourGradient.java @@ -27,6 +27,8 @@ import jalview.datamodel.Annotation; import jalview.datamodel.GraphLine; import jalview.datamodel.SequenceCollectionI; import jalview.datamodel.SequenceI; +import jalview.renderer.AnnotationRenderer; +import jalview.util.Comparison; import java.awt.Color; import java.util.IdentityHashMap; @@ -40,15 +42,25 @@ public class AnnotationColourGradient extends FollowerColourScheme public static final int ABOVE_THRESHOLD = 1; - public AlignmentAnnotation annotation; + private final AlignmentAnnotation annotation; - int aboveAnnotationThreshold = -1; + private final int aboveAnnotationThreshold; public boolean thresholdIsMinMax = false; - GraphLine annotationThreshold; + private GraphLine annotationThreshold; - float r1, g1, b1, rr, gg, bb; + private int redMin; + + private int greenMin; + + private int blueMin; + + private int redRange; + + private int greenRange; + + private int blueRange; private boolean predefinedColours = false; @@ -61,7 +73,7 @@ public class AnnotationColourGradient extends FollowerColourScheme */ private boolean noGradient = false; - IdentityHashMap seqannot = null; + private IdentityHashMap seqannot = null; @Override public ColourSchemeI getInstance(AnnotatedCollectionI sg, @@ -72,12 +84,12 @@ public class AnnotationColourGradient extends FollowerColourScheme acg.thresholdIsMinMax = thresholdIsMinMax; acg.annotationThreshold = (annotationThreshold == null) ? null : new GraphLine(annotationThreshold); - acg.r1 = r1; - acg.g1 = g1; - acg.b1 = b1; - acg.rr = rr; - acg.gg = gg; - acg.bb = bb; + acg.redMin = redMin; + acg.greenMin = greenMin; + acg.blueMin = blueMin; + acg.redRange = redRange; + acg.greenRange = greenRange; + acg.blueRange = blueRange; acg.predefinedColours = predefinedColours; acg.seqAssociated = seqAssociated; acg.noGradient = noGradient; @@ -109,12 +121,12 @@ public class AnnotationColourGradient extends FollowerColourScheme annotationThreshold = annotation.threshold; } // clear values so we don't get weird black bands... - r1 = 254; - g1 = 254; - b1 = 254; - rr = 0; - gg = 0; - bb = 0; + redMin = 254; + greenMin = 254; + blueMin = 254; + redRange = 0; + greenRange = 0; + blueRange = 0; noGradient = true; checkLimits(); @@ -135,13 +147,13 @@ public class AnnotationColourGradient extends FollowerColourScheme annotationThreshold = annotation.threshold; } - r1 = minColour.getRed(); - g1 = minColour.getGreen(); - b1 = minColour.getBlue(); + redMin = minColour.getRed(); + greenMin = minColour.getGreen(); + blueMin = minColour.getBlue(); - rr = maxColour.getRed() - r1; - gg = maxColour.getGreen() - g1; - bb = maxColour.getBlue() - b1; + redRange = maxColour.getRed() - redMin; + greenRange = maxColour.getGreen() - greenMin; + blueRange = maxColour.getBlue() - blueMin; noGradient = false; checkLimits(); @@ -211,9 +223,9 @@ public class AnnotationColourGradient extends FollowerColourScheme float aamin = 0f, aamax = 0f; - public String getAnnotation() + public AlignmentAnnotation getAnnotation() { - return annotation.label; + return annotation; } public int getAboveThreshold() @@ -235,12 +247,13 @@ public class AnnotationColourGradient extends FollowerColourScheme public Color getMinColour() { - return new Color((int) r1, (int) g1, (int) b1); + return new Color(redMin, greenMin, blueMin); } public Color getMaxColour() { - return new Color((int) (r1 + rr), (int) (g1 + gg), (int) (b1 + bb)); + return new Color(redMin + redRange, greenMin + greenRange, blueMin + + blueRange); } /** @@ -258,137 +271,157 @@ public class AnnotationColourGradient extends FollowerColourScheme } /** - * DOCUMENT ME! + * Returns the colour for a given character and position in a sequence * - * @param n - * DOCUMENT ME! + * @param c + * the residue character * @param j - * DOCUMENT ME! - * - * @return DOCUMENT ME! + * the aligned position + * @param seq + * the sequence + * @return */ @Override public Color findColour(char c, int j, SequenceI seq) { - Color currentColour = Color.white; - AlignmentAnnotation annotation = (seqAssociated && seqannot != null ? seqannot + /* + * locate the annotation we are configured to colour by + */ + AlignmentAnnotation ann = (seqAssociated && seqannot != null ? seqannot .get(seq) : this.annotation); - if (annotation == null) + + /* + * if gap or no annotation at position, no colour (White) + */ + if (ann == null || ann.annotations == null + || j >= ann.annotations.length || ann.annotations[j] == null + || Comparison.isGap(c)) { - return currentColour; + return Color.white; } - // if ((threshold == 0) || aboveThreshold(c, j)) - // { - if (annotation.annotations != null && j < annotation.annotations.length - && annotation.annotations[j] != null - && !jalview.util.Comparison.isGap(c)) + + Annotation aj = ann.annotations[j]; + // 'use original colours' => colourScheme != null + // -> look up colour to be used + // predefined colours => preconfigured shading + // -> only use original colours reference if thresholding enabled & + // minmax exists + // annotation.hasIcons => null or black colours replaced with glyph + // colours + // -> reuse original colours if present + // -> if thresholding enabled then return colour on non-whitespace glyph + + /* + * if threshold applies, and annotation fails the test - no colour (white) + */ + if (annotationThreshold != null) { - Annotation aj = annotation.annotations[j]; - // 'use original colours' => colourScheme != null - // -> look up colour to be used - // predefined colours => preconfigured shading - // -> only use original colours reference if thresholding enabled & - // minmax exists - // annotation.hasIcons => null or black colours replaced with glyph - // colours - // -> reuse original colours if present - // -> if thresholding enabled then return colour on non-whitespace glyph - - if (aboveAnnotationThreshold == NO_THRESHOLD - || (annotationThreshold != null && (aboveAnnotationThreshold == ABOVE_THRESHOLD ? aj.value >= annotationThreshold.value - : aj.value <= annotationThreshold.value))) + if ((aboveAnnotationThreshold == ABOVE_THRESHOLD && aj.value < annotationThreshold.value) + || (aboveAnnotationThreshold == BELOW_THRESHOLD && aj.value > annotationThreshold.value)) { - if (predefinedColours && aj.colour != null - && !aj.colour.equals(Color.black)) - { - currentColour = aj.colour; - } - else if (annotation.hasIcons - && annotation.graph == AlignmentAnnotation.NO_GRAPH) + return Color.white; + } + } + + /* + * If 'use original colours' then return the colour of the annotation + * at the aligned position - computed using the background colour scheme + */ + if (predefinedColours && aj.colour != null + && !aj.colour.equals(Color.black)) + { + return aj.colour; + } + + Color result = Color.white; + if (ann.hasIcons && ann.graph == AlignmentAnnotation.NO_GRAPH) + { + /* + * secondary structure symbol colouring + */ + if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.' + && aj.secondaryStructure != '-') + { + if (getColourScheme() != null) { - if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.' - && aj.secondaryStructure != '-') - { - if (getColourScheme() != null) - { - currentColour = getColourScheme().findColour(c, j, seq, null, - 0f); - } - else - { - if (annotation.isRNA()) - { - currentColour = ColourSchemeProperty.rnaHelices[(int) aj.value]; - } - else - { - currentColour = annotation.annotations[j].secondaryStructure == 'H' ? jalview.renderer.AnnotationRenderer.HELIX_COLOUR - : annotation.annotations[j].secondaryStructure == 'E' ? jalview.renderer.AnnotationRenderer.SHEET_COLOUR - : jalview.renderer.AnnotationRenderer.STEM_COLOUR; - } - } - } - else - { - // - return Color.white; - } + result = getColourScheme().findColour(c, j, seq, null, 0f); } - else if (noGradient) + else { - if (getColourScheme() != null) + if (ann.isRNA()) { - currentColour = getColourScheme().findColour(c, j, seq, null, - 0f); + result = ColourSchemeProperty.rnaHelices[(int) aj.value]; } else { - if (aj.colour != null) - { - currentColour = aj.colour; - } + result = ann.annotations[j].secondaryStructure == 'H' ? AnnotationRenderer.HELIX_COLOUR + : ann.annotations[j].secondaryStructure == 'E' ? AnnotationRenderer.SHEET_COLOUR + : AnnotationRenderer.STEM_COLOUR; } } - else + } + else + { + return Color.white; + } + } + else if (noGradient) + { + if (getColourScheme() != null) + { + result = getColourScheme().findColour(c, j, seq, null, 0f); + } + else + { + if (aj.colour != null) { - currentColour = shadeCalculation(annotation, j); + result = aj.colour; } } - // if (conservationColouring) - // { - // currentColour = applyConservation(currentColour, j); - // } } - // } - return currentColour; + else + { + result = shadeCalculation(ann, j); + } + + return result; } - private Color shadeCalculation(AlignmentAnnotation annotation, int j) + /** + * Returns a graduated colour for the annotation at the given column. If there + * is a threshold value, and it is used as the top/bottom of the colour range, + * and the value satisfies the threshold condition, then a colour + * proportionate to the range from the threshold is calculated. For all other + * cases, a colour proportionate to the annotation's min-max range is + * calulated. Note that thresholding is _not_ done here (a colour is computed + * even if threshold is not passed). + * + * @param ann + * @param col + * @return + */ + Color shadeCalculation(AlignmentAnnotation ann, int col) { - - // calculate a shade float range = 1f; - if (thresholdIsMinMax - && annotation.threshold != null + float value = ann.annotations[col].value; + if (thresholdIsMinMax && ann.threshold != null && aboveAnnotationThreshold == ABOVE_THRESHOLD - && annotation.annotations[j].value >= annotation.threshold.value) + && value >= ann.threshold.value) { - range = (annotation.annotations[j].value - annotation.threshold.value) - / (annotation.graphMax - annotation.threshold.value); + range = (value - ann.threshold.value) + / (ann.graphMax - ann.threshold.value); } - else if (thresholdIsMinMax && annotation.threshold != null + else if (thresholdIsMinMax && ann.threshold != null && aboveAnnotationThreshold == BELOW_THRESHOLD - && annotation.annotations[j].value >= annotation.graphMin) + && value <= ann.threshold.value) { - range = (annotation.annotations[j].value - annotation.graphMin) - / (annotation.threshold.value - annotation.graphMin); + range = (value - ann.graphMin) / (ann.threshold.value - ann.graphMin); } else { - if (annotation.graphMax != annotation.graphMin) + if (ann.graphMax != ann.graphMin) { - range = (annotation.annotations[j].value - annotation.graphMin) - / (annotation.graphMax - annotation.graphMin); + range = (value - ann.graphMin) / (ann.graphMax - ann.graphMin); } else { @@ -396,11 +429,11 @@ public class AnnotationColourGradient extends FollowerColourScheme } } - int dr = (int) (rr * range + r1), dg = (int) (gg * range + g1), db = (int) (bb - * range + b1); + int dr = (int) (redRange * range + redMin); + int dg = (int) (greenRange * range + greenMin); + int db = (int) (blueRange * range + blueMin); return new Color(dr, dg, db); - } public boolean isPredefinedColours() @@ -423,6 +456,16 @@ public class AnnotationColourGradient extends FollowerColourScheme seqAssociated = sassoc; } + public boolean isThresholdIsMinMax() + { + return thresholdIsMinMax; + } + + public void setThresholdIsMinMax(boolean minMax) + { + this.thresholdIsMinMax = minMax; + } + @Override public String getSchemeName() { 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/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index 3547757..47dceec 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -1904,10 +1904,10 @@ public abstract class AlignmentViewport implements AlignViewportI, } /** - * If this is a protein alignment and there are mappings to cDNA, add the cDNA - * consensus annotation. + * If this is a protein alignment and there are mappings to cDNA, adds the + * cDNA consensus annotation and returns true, else returns false. */ - public void initComplementConsensus() + public boolean initComplementConsensus() { if (!alignment.isNucleotide()) { @@ -1934,9 +1934,11 @@ public abstract class AlignmentViewport implements AlignViewportI, "PID for cDNA", new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH); initConsensus(complementConsensus); + return true; } } } + return false; } private void initConsensus(AlignmentAnnotation aa) diff --git a/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java b/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java index b184ff2..e0f7f70 100644 --- a/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java +++ b/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java @@ -69,6 +69,7 @@ public class DasSourceRegistry implements DasSourceRegistryI, return loadingDasSources; } + @Override public String getDasRegistryURL() { String registry = jalview.bin.Cache.getDefault("DAS_REGISTRY_URL", @@ -150,9 +151,8 @@ public class DasSourceRegistry implements DasSourceRegistryI, return dsrc; } catch (Exception ex) { - System.err.println("Failed to contact DAS1 registry at " - + registryURL); - ex.printStackTrace(); + System.out.println("DAS1 registry at " + registryURL + + " no longer exists"); return new ArrayList(); } } diff --git a/test/jalview/datamodel/AlignmentTest.java b/test/jalview/datamodel/AlignmentTest.java index 68933bd..c5d09c1 100644 --- a/test/jalview/datamodel/AlignmentTest.java +++ b/test/jalview/datamodel/AlignmentTest.java @@ -1260,4 +1260,13 @@ public class AlignmentTest assertEquals(a.getHiddenSequences().getSize(), 1); } + @Test( + groups = "Functional", + expectedExceptions = { IllegalArgumentException.class }) + public void testSetDataset_selfReference() + { + SequenceI seq = new Sequence("a", "a"); + AlignmentI alignment = new Alignment(new SequenceI[] { seq }); + alignment.setDataset(alignment); + } } diff --git a/test/jalview/datamodel/SequenceGroupTest.java b/test/jalview/datamodel/SequenceGroupTest.java index 65549f2..6e1c2db 100644 --- a/test/jalview/datamodel/SequenceGroupTest.java +++ b/test/jalview/datamodel/SequenceGroupTest.java @@ -10,6 +10,8 @@ import static org.testng.Assert.fail; import jalview.schemes.NucleotideColourScheme; +import junit.extensions.PA; + import org.testng.annotations.Test; public class SequenceGroupTest @@ -111,6 +113,21 @@ public class SequenceGroupTest // expected assertNull(sg3.getContext()); } + + /* + * use PrivilegedAccessor to 'force' a SequenceGroup with + * a circular context reference + */ + PA.setValue(sg2, "context", sg2); + try + { + sg3.setContext(sg2); // circular reference in sg2 + fail("Expected exception"); + } catch (IllegalArgumentException e) + { + // expected + assertNull(sg3.getContext()); + } } @Test(groups = { "Functional" }) diff --git a/test/jalview/ext/jmol/JmolCommandsTest.java b/test/jalview/ext/jmol/JmolCommandsTest.java index 2c23311..3309adf 100644 --- a/test/jalview/ext/jmol/JmolCommandsTest.java +++ b/test/jalview/ext/jmol/JmolCommandsTest.java @@ -20,16 +20,24 @@ */ package jalview.ext.jmol; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; +import jalview.datamodel.ColumnSelection; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.JvOptionPane; import jalview.gui.SequenceRenderer; +import jalview.schemes.JalviewColourScheme; +import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; +import java.util.HashMap; + import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -60,4 +68,75 @@ public class JmolCommandsTest StructureMappingcommandSet[] commands = JmolCommands .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel); } + + @Test(groups = { "Functional" }) + public void testGetColourBySequenceCommands_hiddenColumns() + { + /* + * load these sequences, coloured by Strand propensity, + * with columns 2-4 hidden + */ + SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG"); + SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS"); + AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 }); + AlignFrame af = new AlignFrame(al, 800, 500); + af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString()); + ColumnSelection cs = new ColumnSelection(); + cs.addElement(2); + cs.addElement(3); + cs.addElement(4); + af.getViewport().setColumnSelection(cs); + af.hideSelColumns_actionPerformed(null); + SequenceRenderer sr = new SequenceRenderer(af.getViewport()); + SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } }; + String[] files = new String[] { "seq1.pdb", "seq2.pdb" }; + StructureSelectionManager ssm = new StructureSelectionManager(); + + /* + * map residues 1-10 to residues 21-30 (atoms 105-150) in structures + */ + HashMap map = new HashMap(); + for (int pos = 1; pos <= seq1.getLength(); pos++) + { + map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) }); + } + StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1", + "A", map, null); + ssm.addStructureMapping(sm1); + StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2", + "B", map, null); + ssm.addStructureMapping(sm2); + + StructureMappingcommandSet[] commands = JmolCommands + .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel); + assertEquals(commands.length, 2); + assertEquals(commands[0].commands.length, 1); + + String chainACommand = commands[0].commands[0]; + // M colour is #82827d == (130, 130, 125) (see strand.html help page) + assertTrue(chainACommand + .contains(";select 21:A/1.1;color[130,130,125]")); + // H colour is #60609f == (96, 96, 159) + assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]")); + // hidden columns are Gray (128, 128, 128) + assertTrue(chainACommand + .contains(";select 23-25:A/1.1;color[128,128,128]")); + // S and G are both coloured #4949b6 == (73, 73, 182) + assertTrue(chainACommand + .contains(";select 26-30:A/1.1;color[73,73,182]")); + + String chainBCommand = commands[1].commands[0]; + // M colour is #82827d == (130, 130, 125) + assertTrue(chainBCommand + .contains(";select 21:B/2.1;color[130,130,125]")); + // V colour is #ffff00 == (255, 255, 0) + assertTrue(chainBCommand +.contains(";select 22:B/2.1;color[255,255,0]")); + // hidden columns are Gray (128, 128, 128) + assertTrue(chainBCommand + .contains(";select 23-25:B/2.1;color[128,128,128]")); + // S and G are both coloured #4949b6 == (73, 73, 182) + assertTrue(chainBCommand + .contains(";select 26-30:B/2.1;color[73,73,182]")); + } } diff --git a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java index 49a951e..2c973ca 100644 --- a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java @@ -210,7 +210,7 @@ public class ChimeraCommandsTest assertTrue(theCommand.contains("color #82827d #0:21.A|#1:21.B")); // H colour is #60609f assertTrue(theCommand.contains("color #60609f #0:22.A")); - // V colour is ##ffff00 + // V colour is #ffff00 assertTrue(theCommand.contains("color #ffff00 #1:22.B")); // hidden columns are Gray (128, 128, 128) assertTrue(theCommand.contains("color #808080 #0:23-25.A|#1:23-25.B")); diff --git a/test/jalview/gui/AnnotationRowFilterTest.java b/test/jalview/gui/AnnotationRowFilterTest.java new file mode 100644 index 0000000..69a41c5 --- /dev/null +++ b/test/jalview/gui/AnnotationRowFilterTest.java @@ -0,0 +1,121 @@ +package jalview.gui; + +import static org.testng.Assert.assertEquals; + +import jalview.bin.Cache; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceI; +import jalview.io.DataSourceType; +import jalview.io.FileLoader; + +import java.util.Vector; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Tests for methods of base class of annotation column or colour chooser + */ +public class AnnotationRowFilterTest +{ + AlignFrame af; + + private AnnotationRowFilter testee; + + @BeforeClass(alwaysRun = true) + public void setUp() + { + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", + Boolean.TRUE.toString()); + Cache.applicationProperties.setProperty( + Preferences.SHOW_AUTOCALC_ABOVE, Boolean.TRUE.toString()); + af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa", + DataSourceType.FILE); + testee = new AnnotationRowFilter(af.viewport, af.alignPanel) + { + @Override + public void valueChanged(boolean updateAllAnnotation) + { + } + + @Override + public void updateView() + { + } + + @Override + public void reset() + { + } + }; + } + + /** + * Test the method that builds the drop-down list of annotations to choose + * from for colour by annotation or select columns by annotation + */ + @Test(groups = "Functional") + public void testGetAnnotationItems() + { + AlignmentI al = af.getViewport().getAlignment(); + SequenceI seq1 = al.findSequenceMatch("FER_CAPAA")[0]; + SequenceI seq2 = al.findSequenceMatch("FER_BRANA")[0]; + + AlignmentAnnotation ann1 = new AlignmentAnnotation("ann1Label", "ann1", + null); + al.addAnnotation(ann1); + AlignmentAnnotation ann2 = new AlignmentAnnotation("Significance", + "ann2", null); + al.addAnnotation(ann2); + /* + * a second Significance alignment annotation + */ + AlignmentAnnotation ann2a = new AlignmentAnnotation("Significance", + "ann2", null); + al.addAnnotation(ann2a); + + AlignmentAnnotation ann3 = new AlignmentAnnotation("Jronn", "Jronn", + null); + ann3.setSequenceRef(seq1); + al.addAnnotation(ann3); + AlignmentAnnotation ann4 = new AlignmentAnnotation("Jronn", "Jronn", + null); + ann4.setSequenceRef(seq2); + al.addAnnotation(ann4); + AlignmentAnnotation ann5 = new AlignmentAnnotation("Jnet", "Jnet", null); + ann5.setSequenceRef(seq2); + al.addAnnotation(ann5); + /* + * a second Jnet annotation for FER_BRANA + */ + AlignmentAnnotation ann6 = new AlignmentAnnotation("Jnet", "Jnet", null); + ann6.setSequenceRef(seq2); + al.addAnnotation(ann6); + + /* + * drop-down items with 'Per-sequence only' not checked + */ + Vector items = testee.getAnnotationItems(false); + assertEquals( + items.toString(), + "[Conservation, Quality, Consensus, Occupancy, ann1Label, Significance, Significance_1, Jronn_FER_CAPAA, Jronn_FER_BRANA, Jnet_FER_BRANA, Jnet_FER_BRANA_2]"); + assertEquals(testee.getAnnotationMenuLabel(ann1), "ann1Label"); + assertEquals(testee.getAnnotationMenuLabel(ann2), "Significance"); + assertEquals(testee.getAnnotationMenuLabel(ann2a), "Significance_1"); + assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn_FER_CAPAA"); + assertEquals(testee.getAnnotationMenuLabel(ann4), "Jronn_FER_BRANA"); + assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet_FER_BRANA"); + assertEquals(testee.getAnnotationMenuLabel(ann6), "Jnet_FER_BRANA_2"); + + /* + * drop-down items with 'Per-sequence only' checked + */ + items = testee.getAnnotationItems(true); + assertEquals(items.toString(), "[Jronn, Jnet]"); + // the first annotation of the type is associated with the menu item + assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn"); + assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet"); + } +} diff --git a/test/jalview/schemes/AnnotationColourGradientTest.java b/test/jalview/schemes/AnnotationColourGradientTest.java new file mode 100644 index 0000000..1c93856 --- /dev/null +++ b/test/jalview/schemes/AnnotationColourGradientTest.java @@ -0,0 +1,300 @@ +package jalview.schemes; + +import static org.testng.Assert.assertEquals; + +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.Annotation; +import jalview.datamodel.GraphLine; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; + +import java.awt.Color; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class AnnotationColourGradientTest +{ + final static int WIDTH = 11; + + final static int THRESHOLD_FIVE = 5; + + private AlignmentAnnotation ann; + + private SequenceI seq; + + private AlignmentI al; + + Color minColour = new Color(50, 200, 150); + + Color maxColour = new Color(150, 100, 250); + + /** + * Setup creates an annotation over 11 columns with values 0-10 and threshold + * 5 + */ + @BeforeClass(alwaysRun = true) + public void setUp() + { + Annotation[] anns = new Annotation[WIDTH]; + /* + * set annotations with values 0-10, graded colours + */ + for (int col = 0; col < WIDTH; col++) + { + int hue = col * 20; + Color colour = new Color(hue, hue, hue); + anns[col] = new Annotation("a", "a", 'a', col, colour); + } + + seq = new Sequence("", ""); + al = new Alignment(new SequenceI[]{ seq}); + + /* + * AlignmentAnnotation constructor works out min-max range + */ + ann = new AlignmentAnnotation("", "", anns); + ann.setThreshold(new GraphLine(THRESHOLD_FIVE, "", Color.RED)); + seq.addAlignmentAnnotation(ann); + } + + @Test(groups = "Functional") + public void testShadeCalculation_noThreshold() + { + AnnotationColourGradient testee = new AnnotationColourGradient(ann, + minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD); + for (int col = 0; col < WIDTH; col++) + { + Color result = testee.shadeCalculation(ann, col); + /* + * column is n/10 of the way from minCol to maxCol + */ + Color expected = new Color(50 + 10 * col, 200 - 10 * col, + 150 + 10 * col); + assertEquals(result, expected, "for column " + col); + } + } + + /** + * Test the 'colour above threshold' case + */ + @Test(groups = "Functional") + public void testShadeCalculation_aboveThreshold() + { + AnnotationColourGradient testee = new AnnotationColourGradient(ann, + minColour, maxColour, AnnotationColourGradient.ABOVE_THRESHOLD); + for (int col = 0; col < WIDTH; col++) + { + Color result = testee.shadeCalculation(ann, col); + /* + * colour is derived regardless of the threshold value + * (the renderer will suppress colouring if above/below threshold) + */ + Color expected = new Color(50 + 10 * col, 200 - 10 * col, + 150 + 10 * col); + assertEquals(result, expected, "for column " + col); + } + + /* + * now make 6-10 the span of the colour range + * (annotation value == column number in this test) + */ + testee.setThresholdIsMinMax(true); + for (int col = 0; col < THRESHOLD_FIVE; col++) + { + /* + * colours below the threshold are computed as before + */ + Color expected = new Color(50 + 10 * col, 200 - 10 * col, + 150 + 10 * col); + Color result = testee.shadeCalculation(ann, col); + assertEquals(result, expected, "for column " + col); + } + for (int col = THRESHOLD_FIVE; col < WIDTH; col++) + { + /* + * colours for values >= threshold are graduated + * range is 6-10 so steps of 100/5 = 20 + */ + int factor = col - THRESHOLD_FIVE; + Color expected = new Color(50 + 20 * factor, 200 - 20 * factor, + 150 + 20 * factor); + Color result = testee.shadeCalculation(ann, col); + assertEquals(result, expected, "for column " + col); + } + } + + /** + * Test the 'colour below threshold' case + */ + @Test(groups = "Functional") + public void testShadeCalculation_belowThreshold() + { + AnnotationColourGradient testee = new AnnotationColourGradient(ann, + minColour, maxColour, AnnotationColourGradient.BELOW_THRESHOLD); + + for (int col = 0; col < WIDTH; col++) + { + Color result = testee.shadeCalculation(ann, col); + /* + * colour is derived regardless of the threshold value + * (the renderer will suppress colouring if above/below threshold) + */ + Color expected = new Color(50 + 10 * col, 200 - 10 * col, + 150 + 10 * col); + assertEquals(result, expected, "for column " + col); + } + + /* + * now make 0-5 the span of the colour range + * (annotation value == column number in this test) + */ + testee.setThresholdIsMinMax(true); + for (int col = THRESHOLD_FIVE + 1; col < WIDTH; col++) + { + /* + * colours above the threshold are computed as before + */ + Color expected = new Color(50 + 10 * col, 200 - 10 * col, + 150 + 10 * col); + Color result = testee.shadeCalculation(ann, col); + assertEquals(result, expected, "for column " + col); + } + + for (int col = 0; col <= THRESHOLD_FIVE; col++) + { + /* + * colours for values <= threshold are graduated + * range is 0-5 so steps of 100/5 = 20 + */ + Color expected = new Color(50 + 20 * col, 200 - 20 * col, + 150 + 20 * col); + Color result = testee.shadeCalculation(ann, col); + assertEquals(result, expected, "for column " + col); + } + } + + /** + * Test the 'colour above threshold' case + */ + @Test(groups = "Functional") + public void testFindColour_aboveThreshold() + { + AnnotationColourGradient testee = new AnnotationColourGradient(ann, + minColour, maxColour, AnnotationColourGradient.ABOVE_THRESHOLD); + testee = (AnnotationColourGradient) testee.getInstance(al, null); + + for (int col = 0; col < WIDTH; col++) + { + Color result = testee.findColour('a', col, seq); + /* + * expect white below threshold of 5 + */ + Color expected = col < 5 ? Color.white : new Color(50 + 10 * col, + 200 - 10 * col, + 150 + 10 * col); + assertEquals(result, expected, "for column " + col); + } + + /* + * now make 6-10 the span of the colour range + * (annotation value == column number in this test) + */ + testee.setThresholdIsMinMax(true); + for (int col = 0; col < WIDTH; col++) + { + /* + * colours for values >= threshold are graduated + * range is 6-10 so steps of 100/5 = 20 + */ + int factor = col - THRESHOLD_FIVE; + Color expected = col < 5 ? Color.white : new Color(50 + 20 * factor, + 200 - 20 * factor, + 150 + 20 * factor); + Color result = testee.findColour('a', col, seq); + assertEquals(result, expected, "for column " + col); + } + } + + /** + * Test the 'colour below threshold' case + */ + @Test(groups = "Functional") + public void testFindColour_belowThreshold() + { + AnnotationColourGradient testee = new AnnotationColourGradient(ann, + minColour, maxColour, AnnotationColourGradient.BELOW_THRESHOLD); + testee = (AnnotationColourGradient) testee.getInstance(al, null); + + for (int col = 0; col < WIDTH; col++) + { + Color result = testee.findColour('a', col, seq); + Color expected = col > 5 ? Color.white : new Color(50 + 10 * col, + 200 - 10 * col, 150 + 10 * col); + assertEquals(result, expected, "for column " + col); + } + + /* + * now make 0-5 the span of the colour range + * (annotation value == column number in this test) + */ + testee.setThresholdIsMinMax(true); + for (int col = 0; col < WIDTH; col++) + { + /* + * colours for values <= threshold are graduated + * range is 0-5 so steps of 100/5 = 20 + */ + Color expected = col > 5 ? Color.white : new Color(50 + 20 * col, + 200 - 20 * col, 150 + 20 * col); + Color result = testee.findColour('a', col, seq); + assertEquals(result, expected, "for column " + col); + } + } + + @Test(groups = "Functional") + public void testFindColour_noThreshold() + { + AnnotationColourGradient testee = new AnnotationColourGradient(ann, + minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD); + testee = (AnnotationColourGradient) testee.getInstance(al, null); + + for (int col = 0; col < WIDTH; col++) + { + Color result = testee.findColour('a', col, seq); + /* + * column is n/10 of the way from minCol to maxCol + */ + Color expected = new Color(50 + 10 * col, 200 - 10 * col, + 150 + 10 * col); + assertEquals(result, expected, "for column " + col); + } + } + + @Test(groups = "Functional") + public void testFindColour_originalColours() + { + AnnotationColourGradient testee = new AnnotationColourGradient(ann, + minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD); + testee = (AnnotationColourGradient) testee.getInstance(al, null); + + /* + * flag corresponding to 'use original colours' checkbox + * - just use the individual annotation colours + */ + testee.setPredefinedColours(true); + + /* + * the annotation colour is returned, except for column 0 where it is + * black - in this case the colour scheme colour overrides it + */ + for (int col = 0; col < WIDTH; col++) + { + int hue = col * 20; + Color c = col == 0 ? minColour : new Color(hue, hue, hue); + assertEquals(testee.findColour('a', col, seq), c, "for column " + col); + } + } +} diff --git a/test/junit/extensions/PA.java b/test/junit/extensions/PA.java new file mode 100644 index 0000000..57c873f --- /dev/null +++ b/test/junit/extensions/PA.java @@ -0,0 +1,331 @@ +/* + * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com) + * + * 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. + */ +package junit.extensions; + +import java.util.Collection; + +/** + * This class is used to access a method or field of an object no matter what the access modifier of the method or field. The syntax + * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection. + *

+ * a.k.a. The "ObjectMolester" + *

+ * Here is an example of using this to access a private member:
+ * Given the following class MyClass:
+ * + *

+ * public class MyClass {
+ *    private String name; // private attribute
+ * 
+ *    // private constructor
+ *    private MyClass() {
+ *       super();
+ *    }
+ * 
+ *    // private method
+ *    private void setName(String newName) {
+ *       this.name = newName;
+ *    }
+ * }
+ * 
+ * + * We now want to access the class:
+ * + *
+ * MyClass myObj = PA.instantiate(MyClass.class);
+ * PA.invokeMethod(myObj, "setName(java.lang.String)", "myNewName");
+ * String name = PA.getValue(myObj, "name");
+ * 
+ * + * This class extends {@link PrivilegedAccessor} by re-throwing checked {@link Exception}s as {@link RuntimeException}s. + * + * + * @see PrivilegedAccessor + * + * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com) + * @author Lubos Bistak (lubos@bistak.sk) + */ +public class PA { + private final Object instanceOrClass; + + /** + * Private constructor to make it impossible to instantiate this class from outside of PA. + * + * @param instanceOrClass + */ + private PA(Object instanceOrClass) { + this.instanceOrClass = instanceOrClass; + } + + /** + * Returns a string representation of the given object. The string has the following format: " {}" + * whereas is a comma separated list with = includes + * all attributes of the objects class followed by the attributes of its superclass (if any) and so on. + * + * @param instanceOrClass the object or class to get a string representation of + * @return a string representation of the given object + * + * @see PrivilegedAccessor#toString(Object) + */ + public static String toString(final Object instanceOrClass) { + return PrivilegedAccessor.toString(instanceOrClass); + } + + /** + * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all + * fields (public, private, protected, default) of all its super classes. + * + * @param instanceOrClass the instance or class to get the fields of + * @return the collection of field names of the given instance or class + * + * @see PrivilegedAccessor#getFieldNames(Object) + */ + public static Collection getFieldNames(final Object instanceOrClass) { + return PrivilegedAccessor.getFieldNames(instanceOrClass); + } + + /** + * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well + * all methods (public, private, protected, default) of all its super classes. This does not include constructors. + * + * @param instanceOrClass the instance or class to get the method signatures of + * @return the collection of method signatures of the given instance or class + * + * @see PrivilegedAccessor#getMethodSignatures(Object) + */ + public static Collection getMethodSignatures(final Object instanceOrClass) { + return PrivilegedAccessor.getMethodSignatures(instanceOrClass); + } + + /** + * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned. + * + * @param instanceOrClass the instance or class to get the field from + * @param fieldName the name of the field + * @return an object representing the value of the field + * @throws IllegalArgumentException if the field does not exist + * + * @see PrivilegedAccessor#getValue(Object,String) + */ + public static Object getValue(final Object instanceOrClass, final String fieldName) { + try { + return PrivilegedAccessor.getValue(instanceOrClass, fieldName); + } catch (Exception e) { + throw new IllegalArgumentException("Can't get value of " + fieldName + " from " + instanceOrClass, e); + } + } + + /** + * Gets the value of the named field and returns it as an object. + * + * @param fieldName the name of the field + * @return an object representing the value of the field + * @throws IllegalArgumentException if the field does not exist + * + * @see PA#getValue(Object,String) + */ + public Object getValue(final String fieldName) { + return PA.getValue(instanceOrClass, fieldName); + } + + /** + * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a + * member class, you must provide the object it is a member of as first argument. + * + * @param fromClass the class to instantiate an object from + * @param arguments the arguments to pass to the constructor + * @param argumentTypes the fully qualified types of the arguments of the constructor + * @return an object of the given type + * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal + * parameters differ; if an unwrapping conversion for primitive arguments fails; if, after possible unwrapping, a + * parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if + * this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the + * underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the + * underlying constructor represents an abstract class. + * + * @see PrivilegedAccessor#instantiate(Class,Class[],Object[]) + */ + public static T instantiate(final Class fromClass, final Class[] argumentTypes, final Object... arguments) { + try { + return PrivilegedAccessor.instantiate(fromClass, argumentTypes, correctVarargs(arguments)); + } catch (Exception e) { + throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e); + } + } + + /** + * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide + * the object it is a member of as first argument. + * + * @param fromClass the class to instantiate an object from + * @param arguments the arguments to pass to the constructor + * @return an object of the given type + * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal + * parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a + * parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if + * this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the + * underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the + * underlying constructor represents an abstract class. + * + * @see PrivilegedAccessor#instantiate(Class,Object[]) + */ + public static T instantiate(final Class fromClass, final Object... arguments) { + try { + return PrivilegedAccessor.instantiate(fromClass, correctVarargs(arguments)); + } catch (Exception e) { + throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e); + } + } + + /** + * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for + * primitives. + * + * @param instanceOrClass the instance or class to invoke the method on + * @param methodSignature the name of the method and the parameters
+ * (e.g. "myMethod(java.lang.String, com.company.project.MyObject)") + * @param arguments an array of objects to pass as arguments + * @return the return value of this method or null if void + * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the + * underlying method throws an exception; if no method with the given methodSignature could be found; or if + * an argument couldn't be converted to match the expected type + * + * @see PrivilegedAccessor#invokeMethod(Object,String,Object[]) + */ + public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object... arguments) { + try { + return PrivilegedAccessor.invokeMethod(instanceOrClass, methodSignature, correctVarargs(arguments)); + } catch (Exception e) { + throw new IllegalArgumentException("Can't invoke method " + methodSignature + " on " + instanceOrClass + " with arguments " + + arguments, e); + } + } + + /** + * Calls a method with the given arguments. Arguments can be object types or representations for primitives. + * + * @param methodSignature the name of the method and the parameters
+ * (e.g. "myMethod(java.lang.String, com.company.project.MyObject)") + * @param arguments an array of objects to pass as arguments + * @return the return value of this method or null if void + * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the + * underlying method throws an exception; if no method with the given methodSignature could be found; or if + * an argument couldn't be converted to match the expected type + * @see PA#invokeMethod(Object, String, Object...) + */ + public Object invokeMethod(final String methodSignature, final Object... arguments) { + return PA.invokeMethod(instanceOrClass, methodSignature, arguments); + } + + /** + * Corrects varargs to their initial form. If you call a method with an object-array as last argument the Java varargs mechanism + * converts this array in single arguments. This method returns an object array if the arguments are all of the same type. + * + * @param arguments the possibly converted arguments of a vararg method + * @return arguments possibly converted + */ + private static Object[] correctVarargs(final Object... arguments) { + if ((arguments == null) || changedByVararg(arguments)) return new Object[] {arguments}; + return arguments; + } + + /** + * Tests if the arguments were changed by vararg. Arguments are changed by vararg if they are of a non primitive array type. E.g. + * arguments[] = Object[String[]] is converted to String[] while e.g. arguments[] = Object[int[]] is not converted and stays + * Object[int[]] + * + * Unfortunately we can't detect the difference for arg = Object[primitive] since arguments[] = Object[Object[primitive]] which is + * converted to Object[primitive] and arguments[] = Object[primitive] which stays Object[primitive] + * + * and we can't detect the difference for arg = Object[non primitive] since arguments[] = Object[Object[non primitive]] is converted + * to Object[non primitive] and arguments[] = Object[non primitive] stays Object[non primitive] + * + * @param parameters the parameters + * @return true if parameters were changes by varargs, false otherwise + */ + private static boolean changedByVararg(final Object[] parameters) { + if ((parameters.length == 0) || (parameters[0] == null)) return false; + + if (parameters.getClass() == Object[].class) return false; + + return true; + } + + /** + * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the + * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields + * at other times than instantiation can have unpredictable effects.
+ *
+ * Example:
+ *
+ * + * String myString = "Test";
+ *
+ * //setting the private field value
+ * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});
+ *
+ * //setting the static final field serialVersionUID - MIGHT FAIL
+ * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);
+ *
+ *
+ * + * @param instanceOrClass the instance or class to set the field + * @param fieldName the name of the field + * @param value the new value of the field + * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given + * fieldName can be found; or if the field was final + * + * @see PrivilegedAccessor.setValue(Object,String,Object) + */ + public static PA setValue(final Object instanceOrClass, final String fieldName, final Object value) { + try { + PrivilegedAccessor.setValue(instanceOrClass, fieldName, value); + } catch (Exception e) { + throw new IllegalArgumentException("Can't set value " + value + " at " + fieldName + " in " + instanceOrClass, e); + } + return new PA(instanceOrClass); + } + + /** + * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the + * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields + * at other times than instantiation can have unpredictable effects.
+ *
+ * Example:
+ *
+ * + * String myString = "Test";
+ *
+ * //setting the private field value
+ * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});
+ *
+ * //setting the static final field serialVersionUID - MIGHT FAIL
+ * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);
+ *
+ *
+ * + * @param fieldName the name of the field + * @param value the new value of the field + * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given + * fieldName can be found; or if the field was final + * + * @see PA.setValue(Object,String,Object) + */ + public PA setValue(final String fieldName, final Object value) { + PA.setValue(instanceOrClass, fieldName, value); + return this; + } +} diff --git a/test/junit/extensions/PrivilegedAccessor.java b/test/junit/extensions/PrivilegedAccessor.java new file mode 100644 index 0000000..23f1c6e --- /dev/null +++ b/test/junit/extensions/PrivilegedAccessor.java @@ -0,0 +1,647 @@ +/* + * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com) + * + * 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. + */ +package junit.extensions; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * This class is used to access a method or field of an object no matter what the access modifier of the method or field. The syntax + * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection. + *

+ * a.k.a. The "ObjectMolester" + *

+ * Here is an example of using this to access a private member:
+ * myObject is an object of type MyClass. setName(String) is a private method of + * MyClass. + * + *

+ * PrivilegedAccessor.invokeMethod(myObject, "setName(java.lang.String)", "newName");
+ * 
+ * + * @author Charlie Hubbard (chubbard@iss.net) + * @author Prashant Dhokte (pdhokte@iss.net) + * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com) + * + * @deprecated use PA instead. PA improves the functionality of PrivilegedAccessor by introducing support for varargs and removal of + * the necessity to catch exceptions. + */ +@Deprecated +final class PrivilegedAccessor +{ + /** + * Private constructor to make it impossible to instantiate this class. + */ + private PrivilegedAccessor() { + assert false : "You mustn't instantiate PrivilegedAccessor, use its methods statically"; + } + + /** + * Returns a string representation of the given object. The string has the following format: " {}" + * whereas is a comma separated list with = includes + * all attributes of the objects class followed by the attributes of its superclass (if any) and so on. + * + * @param instanceOrClass the object or class to get a string representation of + * @return a string representation of the given object + */ + public static String toString(final Object instanceOrClass) { + Collection fields = getFieldNames(instanceOrClass); + + if (fields.isEmpty()) + { + return getClass(instanceOrClass).getName(); + } + + StringBuffer stringBuffer = new StringBuffer(); + + stringBuffer.append(getClass(instanceOrClass).getName() + " {"); + + for (String fieldName : fields) { + try { + stringBuffer.append(fieldName + "=" + getValue(instanceOrClass, fieldName) + ", "); + } catch (NoSuchFieldException e) { + assert false : "It should always be possible to get a field that was just here"; + } + } + + stringBuffer.replace(stringBuffer.lastIndexOf(", "), stringBuffer.length(), "}"); + return stringBuffer.toString(); + } + + /** + * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all + * fields (public, private, protected, default) of all its super classes. + * + * @param instanceOrClass the instance or class to get the fields of + * @return the collection of field names of the given instance or class + */ + public static Collection getFieldNames(final Object instanceOrClass) { + if (instanceOrClass == null) + { + return Collections.EMPTY_LIST; + } + + Class clazz = getClass(instanceOrClass); + Field[] fields = clazz.getDeclaredFields(); + Collection fieldNames = new ArrayList(fields.length); + + for (Field field : fields) { + fieldNames.add(field.getName()); + } + fieldNames.addAll(getFieldNames(clazz.getSuperclass())); + + return fieldNames; + } + + /** + * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well + * all methods (public, private, protected, default) of all its super classes. This does not include constructors. + * + * @param instanceOrClass the instance or class to get the method signatures of + * @return the collection of method signatures of the given instance or class + */ + public static Collection getMethodSignatures(final Object instanceOrClass) { + if (instanceOrClass == null) + { + return Collections.EMPTY_LIST; + } + + Class clazz = getClass(instanceOrClass); + Method[] methods = clazz.getDeclaredMethods(); + Collection methodSignatures = new ArrayList(methods.length + Object.class.getDeclaredMethods().length); + + for (Method method : methods) { + methodSignatures.add(method.getName() + "(" + getParameterTypesAsString(method.getParameterTypes()) + ")"); + } + methodSignatures.addAll(getMethodSignatures(clazz.getSuperclass())); + + return methodSignatures; + } + + /** + * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned. + * + * @param instanceOrClass the instance or class to get the field from + * @param fieldName the name of the field + * @return an object representing the value of the field + * @throws NoSuchFieldException if the field does not exist + */ + public static Object getValue(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException { + Field field = getField(instanceOrClass, fieldName); + try { + return field.get(instanceOrClass); + } catch (IllegalAccessException e) { + assert false : "getField() should have setAccessible(true), so an IllegalAccessException should not occur in this place"; + return null; + } + } + + /** + * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide + * the object it is a member of as first argument. + * + * @param fromClass the class to instantiate an object from + * @param args the arguments to pass to the constructor + * @return an object of the given type + * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive + * arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal + * parameter type by a method invocation conversion. + * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is + * inaccessible. + * @throws InvocationTargetException if the underlying constructor throws an exception. + * @throws NoSuchMethodException if the constructor could not be found + * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class. + * + * @see PrivilegedAccessor#instantiate(Class,Class[],Object[]) + */ + public static T instantiate(final Class fromClass, final Object[] args) throws IllegalArgumentException, + InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + return instantiate(fromClass, getParameterTypes(args), args); + } + + /** + * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a + * member class, you must provide the object it is a member of as first argument. + * + * + * @param fromClass the class to instantiate an object from + * @param args the arguments to pass to the constructor + * @param argumentTypes the fully qualified types of the arguments of the constructor + * @return an object of the given type + * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive + * arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal + * parameter type by a method invocation conversion. + * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is + * inaccessible. + * @throws InvocationTargetException if the underlying constructor throws an exception. + * @throws NoSuchMethodException if the constructor could not be found + * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class. + * + * @see PrivilegedAccessor#instantiate(Class,Object[]) + */ + public static T instantiate(final Class fromClass, final Class[] argumentTypes, final Object[] args) + throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchMethodException { + return getConstructor(fromClass, argumentTypes).newInstance(args); + } + + /** + * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for + * primitives. + * + * @param instanceOrClass the instance or class to invoke the method on + * @param methodSignature the name of the method and the parameters
+ * (e.g. "myMethod(java.lang.String, com.company.project.MyObject)") + * @param arguments an array of objects to pass as arguments + * @return the return value of this method or null if void + * @throws IllegalAccessException if the method is inaccessible + * @throws InvocationTargetException if the underlying method throws an exception. + * @throws NoSuchMethodException if no method with the given methodSignature could be found + * @throws IllegalArgumentException if an argument couldn't be converted to match the expected type + */ + public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object[] arguments) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + if ((methodSignature.indexOf('(') == -1) || (methodSignature.indexOf('(') >= methodSignature.indexOf(')'))) + { + throw new NoSuchMethodException(methodSignature); + } + Class[] parameterTypes = getParameterTypes(methodSignature); + return getMethod(instanceOrClass, getMethodName(methodSignature), parameterTypes).invoke(instanceOrClass, + getCorrectedArguments(parameterTypes, arguments)); + } + + /** + * Gets the given arguments corrected to match the given methodSignature. Correction is necessary for array arguments not to be + * mistaken by varargs. + * + * @param parameterTypes the method signatue the given arguments should match + * @param arguments the arguments that should be corrected + * @return the corrected arguments + */ + private static Object[] getCorrectedArguments(Class[] parameterTypes, Object[] arguments) { + if (arguments == null) + { + return arguments; + } + if (parameterTypes.length > arguments.length) + { + return arguments; + } + if (parameterTypes.length < arguments.length) + { + return getCorrectedArguments(parameterTypes, new Object[] {arguments}); + } + + Object[] correctedArguments = new Object[arguments.length]; + int currentArgument = 0; + for (Class parameterType : parameterTypes) { + correctedArguments[currentArgument] = getCorrectedArgument(parameterType, arguments[currentArgument]); + currentArgument++; + } + return correctedArguments; + } + + /** + * Gets the given argument corrected to match the given parameterType. Correction is necessary for array arguments not to be + * mistaken by varargs. + * + * @param parameterType the type to match the given argument upon + * @param argument the argument to match the given parameterType + * @return the corrected argument + */ + private static Object getCorrectedArgument(Class parameterType, Object argument) { + if (!parameterType.isArray() || (argument == null)) { + return argument; // normal argument for normal parameterType + } + + if (!argument.getClass().isArray()) { + return new Object[] {argument}; + } + + if (parameterType.equals(argument.getClass())) + { + return argument; // no need to cast + } + + // (typed) array argument for (object) array parameterType, elements need to be casted + Object correctedArrayArgument = Array.newInstance(parameterType.getComponentType(), Array.getLength(argument)); + for (int index = 0; index < Array.getLength(argument); index++) { + if (parameterType.getComponentType().isPrimitive()) { // rely on autoboxing + Array.set(correctedArrayArgument, index, Array.get(argument, index)); + } else { // cast to expected type + try { + Array.set(correctedArrayArgument, index, parameterType.getComponentType().cast(Array.get(argument, index))); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Argument " + argument + " of type " + argument.getClass() + + " does not match expected argument type " + parameterType + "."); + } + } + } + return correctedArrayArgument; + } + + /** + * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the + * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields + * at other times than instantiation can have unpredictable effects.
+ *
+ * Example:
+ *
+ * + * String myString = "Test";
+ *
+ * //setting the private field value
+ * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});
+ *
+ * //setting the static final field serialVersionUID - MIGHT FAIL
+ * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);
+ *
+ *
+ * + * @param instanceOrClass the instance or class to set the field + * @param fieldName the name of the field + * @param value the new value of the field + * @throws NoSuchFieldException if no field with the given fieldName can be found + * @throws IllegalAccessException possibly if the field was final + */ + public static void setValue(final Object instanceOrClass, final String fieldName, final Object value) throws NoSuchFieldException, + IllegalAccessException { + Field field = getField(instanceOrClass, fieldName); + if (Modifier.isFinal(field.getModifiers())) { + PrivilegedAccessor.setValue(field, "modifiers", field.getModifiers() ^ Modifier.FINAL); + } + field.set(instanceOrClass, value); + } + + /** + * Gets the class with the given className. + * + * @param className the name of the class to get + * @return the class for the given className + * @throws ClassNotFoundException if the class could not be found + */ + private static Class getClassForName(final String className) throws ClassNotFoundException { + if (className.indexOf('[') > -1) { + Class clazz = getClassForName(className.substring(0, className.indexOf('['))); + return Array.newInstance(clazz, 0).getClass(); + } + + if (className.indexOf("...") > -1) { + Class clazz = getClassForName(className.substring(0, className.indexOf("..."))); + return Array.newInstance(clazz, 0).getClass(); + } + + try { + return Class.forName(className, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + return getSpecialClassForName(className); + } + } + + /** + * Maps string representation of primitives to their corresponding classes. + */ + private static final Map> PRIMITIVE_MAPPER = new HashMap>(8); + + /** + * Fills the map with all java primitives and their corresponding classes. + */ + static { + PRIMITIVE_MAPPER.put("int", Integer.TYPE); + PRIMITIVE_MAPPER.put("float", Float.TYPE); + PRIMITIVE_MAPPER.put("double", Double.TYPE); + PRIMITIVE_MAPPER.put("short", Short.TYPE); + PRIMITIVE_MAPPER.put("long", Long.TYPE); + PRIMITIVE_MAPPER.put("byte", Byte.TYPE); + PRIMITIVE_MAPPER.put("char", Character.TYPE); + PRIMITIVE_MAPPER.put("boolean", Boolean.TYPE); + } + + /** + * Gets special classes for the given className. Special classes are primitives and "standard" Java types (like String) + * + * @param className the name of the class to get + * @return the class for the given className + * @throws ClassNotFoundException if the class could not be found + */ + private static Class getSpecialClassForName(final String className) throws ClassNotFoundException { + if (PRIMITIVE_MAPPER.containsKey(className)) + { + return PRIMITIVE_MAPPER.get(className); + } + + if (missesPackageName(className)) + { + return getStandardClassForName(className); + } + + throw new ClassNotFoundException(className); + } + + /** + * Gets a 'standard' java class for the given className. + * + * @param className the className + * @return the class for the given className (if any) + * @throws ClassNotFoundException of no 'standard' java class was found for the given className + */ + private static Class getStandardClassForName(String className) throws ClassNotFoundException { + try { + return Class.forName("java.lang." + className, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + try { + return Class.forName("java.util." + className, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e1) { + throw new ClassNotFoundException(className); + } + } + } + + /** + * Tests if the given className possibly misses its package name. + * + * @param className the className + * @return true if the className might miss its package name, otherwise false + */ + private static boolean missesPackageName(String className) { + if (className.contains(".")) + { + return false; + } + if (className.startsWith(className.substring(0, 1).toUpperCase())) + { + return true; + } + return false; + } + + /** + * Gets the constructor for a given class with the given parameters. + * + * @param type the class to instantiate + * @param parameterTypes the types of the parameters + * @return the constructor + * @throws NoSuchMethodException if the method could not be found + */ + private static Constructor getConstructor(final Class type, final Class[] parameterTypes) throws NoSuchMethodException { + Constructor constructor = type.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + return constructor; + } + + /** + * Return the named field from the given instance or class. Returns a static field if instanceOrClass is a class. + * + * @param instanceOrClass the instance or class to get the field from + * @param fieldName the name of the field to get + * @return the field + * @throws NoSuchFieldException if no such field can be found + * @throws InvalidParameterException if instanceOrClass was null + */ + private static Field getField(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException, + InvalidParameterException { + if (instanceOrClass == null) + { + throw new InvalidParameterException("Can't get field on null object/class"); + } + + Class type = getClass(instanceOrClass); + + try { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + if (type.getSuperclass() == null) + { + throw e; + } + return getField(type.getSuperclass(), fieldName); + } + } + + /** + * Gets the class of the given parameter. If the parameter is a class, it is returned, if it is an object, its class is returned + * + * @param instanceOrClass the instance or class to get the class of + * @return the class of the given parameter + */ + private static Class getClass(final Object instanceOrClass) { + if (instanceOrClass instanceof Class) + { + return (Class) instanceOrClass; + } + + return instanceOrClass.getClass(); + } + + /** + * Return the named method with a method signature matching classTypes from the given class. + * + * @param type the class to get the method from + * @param methodName the name of the method to get + * @param parameterTypes the parameter-types of the method to get + * @return the method + * @throws NoSuchMethodException if the method could not be found + */ + private static Method getMethod(final Class type, final String methodName, final Class[] parameterTypes) + throws NoSuchMethodException { + try { + return type.getDeclaredMethod(methodName, parameterTypes); + } catch (NoSuchMethodException e) { + if (type.getSuperclass() == null) + { + throw e; + } + return getMethod(type.getSuperclass(), methodName, parameterTypes); + } + } + + /** + * Gets the method with the given name and parameters from the given instance or class. If instanceOrClass is a class, then we get a + * static method. + * + * @param instanceOrClass the instance or class to get the method of + * @param methodName the name of the method + * @param parameterTypes the parameter-types of the method to get + * @return the method + * @throws NoSuchMethodException if the method could not be found + */ + private static Method getMethod(final Object instanceOrClass, final String methodName, final Class[] parameterTypes) + throws NoSuchMethodException { + Class type; + + type = getClass(instanceOrClass); + + Method accessMethod = getMethod(type, methodName, parameterTypes); + accessMethod.setAccessible(true); + return accessMethod; + } + + /** + * Gets the name of a method. + * + * @param methodSignature the signature of the method + * @return the name of the method + */ + private static String getMethodName(final String methodSignature) { + try { + return methodSignature.substring(0, methodSignature.indexOf('(')).trim(); + } catch (StringIndexOutOfBoundsException e) { + assert false : "Signature must have been checked before this method was called"; + return null; + } + } + + /** + * Gets the types of the parameters. + * + * @param parameters the parameters + * @return the class-types of the arguments + */ + private static Class[] getParameterTypes(final Object[] parameters) { + if (parameters == null) + { + return new Class[0]; + } + + Class[] typesOfParameters = new Class[parameters.length]; + + for (int i = 0; i < parameters.length; i++) { + typesOfParameters[i] = parameters[i].getClass(); + } + return typesOfParameters; + } + + /** + * Gets the types of the given parameters. If the parameters don't match the given methodSignature an IllegalArgumentException is + * thrown. + * + * @param methodSignature the signature of the method + * @return the parameter types as class[] + * @throws NoSuchMethodException if the method could not be found + * @throws IllegalArgumentException if one of the given parameters doesn't math the given methodSignature + */ + private static Class[] getParameterTypes(final String methodSignature) throws NoSuchMethodException, IllegalArgumentException { + String signature = getSignatureWithoutBraces(methodSignature); + + StringTokenizer tokenizer = new StringTokenizer(signature, ", *"); + Class[] typesInSignature = new Class[tokenizer.countTokens()]; + + for (int x = 0; tokenizer.hasMoreTokens(); x++) { + String className = tokenizer.nextToken(); + try { + typesInSignature[x] = getClassForName(className); + } catch (ClassNotFoundException e) { + NoSuchMethodException noSuchMethodException = new NoSuchMethodException(methodSignature); + noSuchMethodException.initCause(e); + throw noSuchMethodException; + } + } + return typesInSignature; + } + + /** + * Gets the parameter types as a string. + * + * @param classTypes the types to get as names. + * @return the parameter types as a string + * + * @see java.lang.Class#argumentTypesToString(Class[]) + */ + private static String getParameterTypesAsString(final Class[] classTypes) { + assert classTypes != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes"; + if (classTypes.length == 0) + { + return ""; + } + + StringBuilder parameterTypes = new StringBuilder(); + for (Class clazz : classTypes) { + assert clazz != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes"; + parameterTypes.append(clazz.getName()).append(", "); + } + + return parameterTypes.substring(0, parameterTypes.length() - 2); + } + + /** + * Removes the braces around the methods signature. + * + * @param methodSignature the signature with braces + * @return the signature without braces + */ + private static String getSignatureWithoutBraces(final String methodSignature) { + try { + return methodSignature.substring(methodSignature.indexOf('(') + 1, methodSignature.indexOf(')')); + } catch (IndexOutOfBoundsException e) { + assert false : "signature must have been checked before this method"; + return null; + } + } + +}