+ }
+ return result;
+ }
+
+ /**
+ * Removes from the list of features any whose group is not shown, or that are
+ * visible and duplicate the location of a visible feature of the same type.
+ * Should be used only for features of the same, simple, feature colour (which
+ * normally implies the same feature type). No filtering is done if
+ * transparency, or any feature filters, are in force.
+ *
+ * @param features
+ */
+ public void filterFeaturesForDisplay(List<SequenceFeature> features)
+ {
+ /*
+ * don't remove 'redundant' features if
+ * - transparency is applied (feature count affects depth of feature colour)
+ * - filters are applied (not all features may be displayable)
+ */
+ if (features.isEmpty() || transparency != 1f
+ || !featureFilters.isEmpty())
+ {
+ return;
+ }
+
+ SequenceFeatures.sortFeatures(features, true);
+ SequenceFeature lastFeature = null;
+
+ Iterator<SequenceFeature> it = features.iterator();
+ while (it.hasNext())
+ {
+ SequenceFeature sf = it.next();
+ if (featureGroupNotShown(sf))
+ {
+ it.remove();
+ continue;
+ }
+
+ /*
+ * a feature is redundant for rendering purposes if it has the
+ * same extent as another (so would just redraw the same colour);
+ * (checking type and isContactFeature as a fail-safe here, although
+ * currently they are guaranteed to match in this context)
+ */
+ if (lastFeature != null
+ && sf.getBegin() == lastFeature.getBegin()
+ && sf.getEnd() == lastFeature.getEnd()
+ && sf.isContactFeature() == lastFeature.isContactFeature()
+ && sf.getType().equals(lastFeature.getType()))
+ {
+ it.remove();
+ }
+ lastFeature = sf;
+ }
+ }
+
+ @Override
+ public Map<String, FeatureMatcherSetI> getFeatureFilters()
+ {
+ return featureFilters;
+ }
+
+ @Override
+ public void setFeatureFilters(Map<String, FeatureMatcherSetI> filters)
+ {
+ featureFilters = filters;
+ }
+
+ @Override
+ public FeatureMatcherSetI getFeatureFilter(String featureType)
+ {
+ return featureFilters.get(featureType);
+ }
+
+ @Override
+ public void setFeatureFilter(String featureType, FeatureMatcherSetI filter)
+ {
+ if (filter == null || filter.isEmpty())
+ {
+ featureFilters.remove(featureType);
+ }
+ else
+ {
+ featureFilters.put(featureType, filter);
+ }
+ }
+
+ /**
+ * Answers the colour for the feature, or null if the feature is excluded by
+ * feature group visibility, by filters, or by colour threshold settings. This
+ * method does not take feature visibility into account.
+ *
+ * @param sf
+ * @param fc
+ * @return
+ */
+ public Color getColor(SequenceFeature sf, FeatureColourI fc)
+ {
+ /*
+ * is the feature group displayed?
+ */
+ if (featureGroupNotShown(sf))
+ {
+ return null;
+ }
+
+ /*
+ * does the feature pass filters?
+ */
+ if (!featureMatchesFilters(sf))
+ {
+ return null;
+ }
+
+ return fc.getColor(sf);
+ }
+
+ /**
+ * Answers true if there no are filters defined for the feature type, or this
+ * feature matches the filters. Answers false if the feature fails to match
+ * filters.
+ *
+ * @param sf
+ * @return
+ */
+ protected boolean featureMatchesFilters(SequenceFeature sf)
+ {
+ FeatureMatcherSetI filter = featureFilters.get(sf.getType());
+ return filter == null ? true : filter.matches(sf);
+ }
+
+ @Override
+ public MappedFeatures findComplementFeaturesAtResidue(
+ final SequenceI sequence, final int pos)
+ {
+ SequenceI ds = sequence.getDatasetSequence();
+ if (ds == null)
+ {
+ ds = sequence;
+ }
+ final char residue = ds.getCharAt(pos - ds.getStart());
+
+ List<SequenceFeature> found = new ArrayList<>();
+ List<AlignedCodonFrame> mappings = this.av.getAlignment()
+ .getCodonFrame(sequence);
+
+ /*
+ * fudge: if no mapping found, check the complementary alignment
+ * todo: only store in one place? StructureSelectionManager?
+ */
+ if (mappings.isEmpty())
+ {
+ mappings = this.av.getCodingComplement().getAlignment()
+ .getCodonFrame(sequence);
+ }
+
+ /*
+ * todo: direct lookup of CDS for peptide and vice-versa; for now,
+ * have to search through an unordered list of mappings for a candidate
+ */
+ Mapping mapping = null;
+ SequenceI mapFrom = null;
+
+ for (AlignedCodonFrame acf : mappings)
+ {
+ mapping = acf.getMappingForSequence(sequence);
+ if (mapping == null || !mapping.getMap().isTripletMap())