Merge branch 'improvement/JAL-3449_add_install4j_installer_getdown_versions_to_jalvie...
authorJim Procter <jprocter@issues.jalview.org>
Wed, 26 Feb 2020 14:52:19 +0000 (14:52 +0000)
committerJim Procter <jprocter@issues.jalview.org>
Wed, 26 Feb 2020 14:52:19 +0000 (14:52 +0000)
16 files changed:
build.gradle
doc/building.md
gradle.properties
help/help/html/releases.html
resources/lang/Messages.properties
src/jalview/analysis/AlignmentUtils.java
src/jalview/api/AlignViewControllerGuiI.java
src/jalview/api/SplitContainerI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/controller/FeatureSettingsControllerGuiI.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/SplitFrame.java
src/jalview/util/MessageManager.java

index 36b75fd..ca73859 100644 (file)
@@ -569,25 +569,43 @@ task cloverInstr() {
 }
 
 
+task cloverReportHTML (type: JavaExec) {
+    inputs.dir "${buildDir}/clover"
+    outputs.dir "${reportsDir}/clover"
+
+    classpath configurations.cloverRuntime
+    maxHeapSize "${cloverReportJVMHeap}"
+    jvmArgs += "${cloverReportJVMArgs}"
+    main = "com.atlassian.clover.reporters.html.HtmlReporter"
+    args  "--initstring", "${buildDir}/clover/clover.db", "-o", "${reportsDir}/clover"
+    "${cloverReportHTMLOptions}".split(",").each {
+      args+= it.trim()
+      }
+}
+
+task cloverReportXML (type: JavaExec) {
+    inputs.dir "${buildDir}/clover"
+    outputs.dir "${reportsDir}/clover"
+    maxHeapSize "${cloverReportJVMHeap}"
+    jvmArgs "${cloverReportJVMArgs}"
+    classpath configurations.cloverRuntime
+    main = "com.atlassian.clover.reporters.xml.XMLReporter"
+    args  "--initstring", "${buildDir}/clover/clover.db", "-o", "${reportsDir}/clover/clover.xml"
+    
+    "${cloverReportXMLOptions}".split(",").each {
+      args+= it.trim()
+      }
+}
 task cloverReport {
   group = "Verification"
-    description = "Createst the Clover report"
+    description = "Creates the Clover report"
     inputs.dir "${buildDir}/clover"
     outputs.dir "${reportsDir}/clover"
     onlyIf {
       file("${buildDir}/clover/clover.db").exists()
     }
-  doFirst {
-    def argsList = ["--initstring", "${buildDir}/clover/clover.db",
-    "-o", "${reportsDir}/clover"]
-    String[] args = argsList.toArray()
-    com.atlassian.clover.reporters.html.HtmlReporter.runReport(args)
-
-    // and generate ${reportsDir}/clover/clover.xml
-    args = ["--initstring", "${buildDir}/clover/clover.db",
-    "-o", "${reportsDir}/clover/clover.xml"].toArray()
-    com.atlassian.clover.reporters.xml.XMLReporter.runReport(args)
-  }
+    dependsOn cloverReportXML
+    dependsOn cloverReportHTML
 }
 // end clover bits
 
index fbc4cca..33e1fec 100644 (file)
@@ -546,5 +546,35 @@ perl -n -e 'm/^\s*<(\w+)[^>]*\bmediaFileName=/ && print "$1\n";' utils/install4j
 in the `jalview` root folder.
 
 
+## Enabling Code Coverage with OpenClover
+
+Bytecode instrumentation tasks are enabled by specifying 'true' (or just a non-whitespace non-numeric word) in the 'clover' property. This adds the 'openclover' plugin to the build script's classpath, making it possible to track code execution during test which can be viewed as an HTML report published at build/reports/clover/index.html.
+
+```gradle -Pclover=true test cloverReport```
+
+#### Troubleshooting report generation
+
+The build forks a new JVM process to run the clover report generation tools (both XML and HTML reports are generated by default). The following properties can be used to specify additional options or adjust JVM memory settings. Default values for these options are:
+
+##### JVM Memory settings - increase if out of memory errors are reported
+
+```cloverReportJVMHeap = 2g```
+
+##### -Dfile.encoding=UTF-8 is an essential parameters for report generation. Add additional ones separated by a space.
+
+```cloverReportJVMArgs = -Dfile.encoding=UTF-8```
+
+##### Add -v to debug velocity html generation errors, or -d to track more detailed issues with the coverage database
+
+```cloverReportHTMLOptions = ```
+
+##### -v for verbose, -d for debug level messages (as above)
+
+```cloverReportXMLOptions = ```
+
+
+_Note_ do not forget to include the -Dfile.encoding=UTF-8 option: this is essential for some platforms in order for Clover to correctly parse some Jalview source files that contain characters that are UTF-8 encoded. 
+
+
 ---
 [Jalview Development Team](mailto:help@jalview.org)
index 7a51138..e4f0ebf 100644 (file)
@@ -34,6 +34,10 @@ classes_dir = classes
 examplesDir = examples
 clover = false
 use_clover = false
+cloverReportJVMHeap = 2g
+cloverReportJVMArgs = -Dfile.encoding=UTF-8
+cloverReportHTMLOptions = 
+cloverReportXMLOptions =
 cloverClassesDir = clover-classes
 cloverSourcesInstrDir = sources-instr
 packageDir = dist
index d7f3ee1..2fa241c 100755 (executable)
@@ -61,6 +61,8 @@ li:before {
           <em>25/2/2020</em></strong></td>
       <td align="left" valign="top">
         <ul>
+          <li><!-- JAL-3121 -->Feature attributes from VCF files can be exported and re-imported as GFF3 files
+          </li>
           <li>
             <!-- JAL-3376 -->Record &quot;fixed column&quot; values POS,
             ID, QUAL, FILTER from VCF as Feature Attributes
index fdd15cd..a1d32e6 100644 (file)
@@ -132,6 +132,8 @@ tooltip.select_highlighted_columns = Press B to mark highlighted columns, Ctrl-(
 action.deselect_all = Deselect all
 action.invert_selection = Invert selection
 action.using_jmol = Using Jmol
+action.undo_changes_to_feature_settings = Undo all unapplied changes to feature settings
+action.undo_changes_to_feature_settings_and_close_the_dialog = Undo all pending changes and close the feature settings dialog
 action.link = Link
 action.group_link = Group Link
 action.show_chain = Show Chain
@@ -228,6 +230,7 @@ label.nucleotide = Nucleotide
 label.protein = Protein
 label.nucleotides = Nucleotides
 label.proteins = Proteins
+label.CDS = CDS
 label.to_new_alignment = To New Alignment
 label.to_this_alignment = Add To This Alignment
 label.apply_colour_to_all_groups = Apply Colour To All Groups
@@ -765,6 +768,9 @@ label.generating_features_for_params = Generating features for - {0}
 label.generating_annotations_for_params = Generating annotations for - {0}
 label.varna_params = VARNA - {0}
 label.sequence_feature_settings = Sequence Feature Settings
+label.sequence_feature_settings_for = Sequence Feature Settings for {0}
+label.sequence_feature_settings_for_view = Sequence Feature Settings for view "{0}"
+label.sequence_feature_settings_for_CDS_and_Protein = Sequence Feature Settings for CDS and Protein
 label.pairwise_aligned_sequences = Pairwise Aligned Sequences
 label.original_data_for_params = Original Data for {0}
 label.points_for_params = Points for {0}
@@ -1401,6 +1407,7 @@ label.create_image_of = Create {0} image of {1}
 label.click_to_edit = Click to edit, right-click for menu
 label.by_annotation_tooltip = Annotation Colour is configured from the main Colour menu
 label.show_linked_features = Show {0} features
+label.show_linked_feature_settings = Open {0} settings
 label.on_top = on top
 label.include_linked_features = Include {0} features
 label.include_linked_tooltip = Include visible {0} features<br>converted to local sequence coordinates
\ No newline at end of file
index fdf66d0..2b88cb0 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.analysis;
 
-import static jalview.io.gff.GffConstants.CLINICAL_SIGNIFICANCE;
-
 import jalview.commands.RemoveGapColCommand;
 import jalview.datamodel.AlignedCodon;
 import jalview.datamodel.AlignedCodonFrame;
@@ -38,7 +36,6 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.SequenceFeatures;
-import jalview.io.gff.Gff3Helper;
 import jalview.io.gff.SequenceOntologyI;
 import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
@@ -46,10 +43,7 @@ import jalview.util.DBRefUtils;
 import jalview.util.IntRangeComparator;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
-import jalview.util.StringUtils;
 
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -1737,15 +1731,8 @@ public class AlignmentUtils
 
           cdsSeqs.add(cdsSeq);
 
-          if (!dataset.getSequences().contains(cdsSeqDss))
-          {
-            // check if this sequence is a newly created one
-            // so needs adding to the dataset
-            dataset.addSequence(cdsSeqDss);
-          }
-
           /*
-           * add a mapping from CDS to the (unchanged) mapped to range
+           * build the mapping from CDS to protein
            */
           List<int[]> cdsRange = Collections
                   .singletonList(new int[]
@@ -1754,16 +1741,26 @@ public class AlignmentUtils
           MapList cdsToProteinMap = new MapList(cdsRange,
                   mapList.getToRanges(), mapList.getFromRatio(),
                   mapList.getToRatio());
-          AlignedCodonFrame cdsToProteinMapping = new AlignedCodonFrame();
-          cdsToProteinMapping.addMap(cdsSeqDss, proteinProduct,
-                  cdsToProteinMap);
 
-          /*
-           * guard against duplicating the mapping if repeating this action
-           */
-          if (!mappings.contains(cdsToProteinMapping))
+          if (!dataset.getSequences().contains(cdsSeqDss))
           {
-            mappings.add(cdsToProteinMapping);
+            /*
+             * if this sequence is a newly created one, add it to the dataset
+             * and made a CDS to protein mapping (if sequence already exists,
+             * CDS-to-protein mapping _is_ the transcript-to-protein mapping)
+             */
+            dataset.addSequence(cdsSeqDss);
+            AlignedCodonFrame cdsToProteinMapping = new AlignedCodonFrame();
+            cdsToProteinMapping.addMap(cdsSeqDss, proteinProduct,
+                  cdsToProteinMap);
+
+            /*
+             * guard against duplicating the mapping if repeating this action
+             */
+            if (!mappings.contains(cdsToProteinMapping))
+            {
+              mappings.add(cdsToProteinMapping);
+            }
           }
 
           propagateDBRefsToCDS(cdsSeqDss, dnaSeq.getDatasetSequence(),
@@ -2576,6 +2573,13 @@ public class AlignmentUtils
     {
       List<SequenceI> alignedSequences = alignedDatasets
               .get(seq.getDatasetSequence());
+      if (alignedSequences.isEmpty())
+      {
+        /*
+         * defensive check - shouldn't happen! (JAL-3536)
+         */
+        continue;
+      }
       SequenceI alignedSeq = alignedSequences.get(0);
 
       /*
index 86e61e7..6549d64 100644 (file)
@@ -23,6 +23,8 @@ package jalview.api;
 import jalview.commands.CommandI;
 import jalview.schemes.ColourSchemeI;
 
+import java.awt.Rectangle;
+
 /**
  * Interface implemented by gui implementations managing a Jalview Alignment
  * View
@@ -62,4 +64,25 @@ public interface AlignViewControllerGuiI
    * @return
    */
   FeatureSettingsControllerI getFeatureSettingsUI();
+
+  /**
+   * displays the Feature Settigns control panel for the alignment view - if one
+   * exists it is closed and re-opened.
+   * 
+   * @return the current feature settings controller
+   */
+  FeatureSettingsControllerI showFeatureSettingsUI();
+
+  /**
+   * record the last position of a feature settings dialog before it was closed
+   * 
+   * @param bounds
+   */
+  void setFeatureSettingsGeometry(Rectangle bounds);
+
+  /**
+   * 
+   * @return last position of feature settings for this alignment view GUI
+   */
+  Rectangle getFeatureSettingsGeometry();
 }
index 46f5f44..6b037f5 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.api;
 
+import jalview.controller.FeatureSettingsControllerGuiI;
 import jalview.datamodel.AlignmentI;
 
 /**
@@ -55,4 +56,41 @@ public interface SplitContainerI
    */
   String getComplementTitle(Object af);
 
+  /**
+   * get the 'other' alignFrame in the SplitFrame
+   * 
+   * @param alignFrame
+   * @return the complement alignFrame - or null if alignFrame wasn't held by this
+   *         frame
+   */
+  AlignViewControllerGuiI getComplementAlignFrame(
+          AlignViewControllerGuiI alignFrame);
+
+  /**
+   * add the given UI to the splitframe's feature settings UI holder
+   * 
+   * @param featureSettings
+   * @return
+   */
+  void addFeatureSettingsUI(
+          FeatureSettingsControllerGuiI featureSettings);
+
+  /**
+   * Request to close all feature settings originating from a particular panel.
+   * 
+   * @param featureSettings
+   * @param closeContainingFrame
+   *                               - if false then the tab containing the feature
+   *                               settings will be 'reset' ready for a new
+   *                               feature settings
+   */
+  void closeFeatureSettings(FeatureSettingsControllerI featureSettings,
+          boolean closeContainingFrame);
+
+  /**
+   * 
+   * @return true if a feature settings panel is currently open
+   */
+  boolean isFeatureSettingsOpen();
+
 }
index 3bb5fe8..0bc45e2 100644 (file)
@@ -95,6 +95,7 @@ import java.awt.Menu;
 import java.awt.MenuBar;
 import java.awt.MenuItem;
 import java.awt.Panel;
+import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusEvent;
@@ -1203,7 +1204,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     }
     else if (source == featureSettings)
     {
-      new FeatureSettings(alignPanel);
+      showFeatureSettingsUI();
     }
     else if (source == alProperties)
     {
@@ -4338,4 +4339,24 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     return alignPanel.av.featureSettings;
   }
 
+  @Override
+  public FeatureSettingsControllerI showFeatureSettingsUI()
+  {
+    return new FeatureSettings(alignPanel);
+  }
+
+  private Rectangle fs_bounds = null;
+
+  @Override
+  public void setFeatureSettingsGeometry(Rectangle bounds)
+  {
+    fs_bounds = bounds;
+  }
+
+  @Override
+  public Rectangle getFeatureSettingsGeometry()
+  {
+    return fs_bounds;
+  }
+
 }
index 728612e..92571bc 100644 (file)
  */
 package jalview.controller;
 
+import jalview.api.AlignViewControllerGuiI;
+
 public interface FeatureSettingsControllerGuiI
 {
 
+  AlignViewControllerGuiI getAlignframe();
+
+  void featureSettings_isClosed();
+
+  /**
+   * undo any changes made to feature settings whilst the dialog has been visible,
+   * since the last 'apply'
+   */
+  void revert();
+
 }
index fcb6572..b7d8b3a 100644 (file)
@@ -1479,9 +1479,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           closeView(alignPanel);
         }
       }
-
       if (closeAllTabs)
       {
+        if (featureSettings != null && featureSettings.isOpen())
+        {
+          featureSettings.close();
+          featureSettings = null;
+        }
         /*
          * this will raise an INTERNAL_FRAME_CLOSED event and this method will
          * be called recursively, with the frame now in 'closed' state
@@ -3187,9 +3191,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void featureSettings_actionPerformed(ActionEvent e)
   {
+    showFeatureSettingsUI();
+  }
+
+  @Override
+  public FeatureSettingsControllerI showFeatureSettingsUI()
+  {
     if (featureSettings != null)
     {
-      featureSettings.close();
+      featureSettings.closeOldSettings();
       featureSettings = null;
     }
     if (!showSeqFeatures.isSelected())
@@ -3199,6 +3209,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       showSeqFeatures_actionPerformed(null);
     }
     featureSettings = new FeatureSettings(this);
+    return featureSettings;
   }
 
   /**
@@ -4683,7 +4694,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           {
             if (parseFeaturesFile(file, sourceType))
             {
-              alignPanel.paintAlignment(true, true);
+              SplitFrame splitFrame = (SplitFrame) getSplitViewContainer();
+              if (splitFrame != null)
+              {
+                splitFrame.repaint();
+              }
+              else
+              {
+                alignPanel.paintAlignment(true, true);
+              }
             }
           }
           else
@@ -4739,6 +4758,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       viewport = alignPanel.av;
       avc.setViewportAndAlignmentPanel(viewport, alignPanel);
       setMenusFromViewport(viewport);
+      if (featureSettings != null && featureSettings.isOpen()
+              && featureSettings.fr.getViewport() != viewport)
+      {
+        if (viewport.isShowSequenceFeatures())
+        {
+          // refresh the featureSettings to reflect UI change
+          showFeatureSettingsUI();
+        }
+        else
+        {
+          // close feature settings for this view.
+          featureSettings.close();
+        }
+      }
+
     }
 
     /*
@@ -5658,6 +5692,19 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
   }
+
+  private Rectangle lastFeatureSettingsBounds = null;
+  @Override
+  public void setFeatureSettingsGeometry(Rectangle bounds)
+  {
+    lastFeatureSettingsBounds = bounds;
+  }
+
+  @Override
+  public Rectangle getFeatureSettingsGeometry()
+  {
+    return lastFeatureSettingsBounds;
+  }
 }
 
 class PrintThread extends Thread
index 91af323..888ffcd 100644 (file)
@@ -79,9 +79,9 @@ public class AlignViewport extends AlignmentViewport
 
   boolean antiAlias = false;
 
-  private Rectangle explodedGeometry;
+  private Rectangle explodedGeometry = null;
 
-  private String viewName;
+  private String viewName = null;
 
   /*
    * Flag set true on the view that should 'gather' multiple views of the same
index 1ec6939..1b8dda7 100644 (file)
@@ -1923,11 +1923,28 @@ public class Desktop extends jalview.jbgui.GDesktop
       return;
     }
 
+    // FIXME: ideally should use UI interface API
+    FeatureSettings viewFeatureSettings = (af.featureSettings != null
+            && af.featureSettings.isOpen())
+            ? af.featureSettings
+            : null;
+    Rectangle fsBounds = af.getFeatureSettingsGeometry();
     for (int i = 0; i < size; i++)
     {
       AlignmentPanel ap = af.alignPanels.get(i);
+
       AlignFrame newaf = new AlignFrame(ap);
 
+      // transfer reference for existing feature settings to new alignFrame
+      if (ap == af.alignPanel)
+      {
+        if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
+        {
+          newaf.featureSettings = viewFeatureSettings;
+        }
+        newaf.setFeatureSettingsGeometry(fsBounds);
+      }
+
       /*
        * Restore the view's last exploded frame geometry if known. Multiple
        * views from one exploded frame share and restore the same (frame)
@@ -1943,8 +1960,17 @@ public class Desktop extends jalview.jbgui.GDesktop
 
       addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
               AlignFrame.DEFAULT_HEIGHT);
+      // and materialise a new feature settings dialog instance for the new alignframe
+      // (closes the old as if 'OK' was pressed)
+      if (ap == af.alignPanel && newaf.featureSettings != null
+              && newaf.featureSettings.isOpen()
+              && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
+      {
+        newaf.showFeatureSettingsUI();
+      }
     }
 
+    af.featureSettings = null;
     af.alignPanels.clear();
     af.closeMenuItem_actionPerformed(true);
 
@@ -1964,7 +1990,6 @@ public class Desktop extends jalview.jbgui.GDesktop
     source.viewport.setExplodedGeometry(source.getBounds());
     JInternalFrame[] frames = desktop.getAllFrames();
     String viewId = source.viewport.getSequenceSetId();
-
     for (int t = 0; t < frames.length; t++)
     {
       if (frames[t] instanceof AlignFrame && frames[t] != source)
@@ -1985,11 +2010,32 @@ public class Desktop extends jalview.jbgui.GDesktop
 
         if (gatherThis)
         {
+          if (af.featureSettings != null && af.featureSettings.isOpen())
+          {
+            if (source.featureSettings == null)
+            {
+              // preserve the feature settings geometry for this frame
+              source.featureSettings = af.featureSettings;
+              source.setFeatureSettingsGeometry(
+                      af.getFeatureSettingsGeometry());
+            }
+            else
+            {
+              // close it and forget
+              af.featureSettings.close();
+            }
+          }
           af.alignPanels.clear();
           af.closeMenuItem_actionPerformed(true);
         }
       }
     }
+    // refresh the feature setting UI for the source frame if it exists
+    if (source.featureSettings != null
+            && source.featureSettings.isOpen())
+    {
+      source.showFeatureSettingsUI();
+    }
 
   }
 
index 11d5e39..3efc63e 100644 (file)
  */
 package jalview.gui;
 
+import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
 import jalview.api.FeatureSettingsControllerI;
+import jalview.api.SplitContainerI;
 import jalview.api.ViewStyleI;
+import jalview.controller.FeatureSettingsControllerGuiI;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.FeatureMatcher;
@@ -79,6 +82,7 @@ import java.util.Map;
 import java.util.Set;
 
 import javax.help.HelpSetException;
+import javax.swing.AbstractButton;
 import javax.swing.AbstractCellEditor;
 import javax.swing.BorderFactory;
 import javax.swing.Icon;
@@ -112,7 +116,7 @@ import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamReader;
 
 public class FeatureSettings extends JPanel
-        implements FeatureSettingsControllerI
+        implements FeatureSettingsControllerI, FeatureSettingsControllerGuiI
 {
   private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
           .getString("label.sequence_feature_colours");
@@ -180,6 +184,26 @@ public class FeatureSettings extends JPanel
    */
   Map<String, float[]> typeWidth = null;
 
+  private void storeOriginalSettings()
+  {
+    // save transparency for restore on Cancel
+    originalTransparency = fr.getTransparency();
+
+    updateTransparencySliderFromFR();
+
+    originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
+    originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
+  }
+
+  private void updateTransparencySliderFromFR()
+  {
+    boolean incon = inConstruction;
+    inConstruction = true;
+
+    int transparencyAsPercent = (int) (fr.getTransparency() * 100);
+    transparency.setValue(100 - transparencyAsPercent);
+    inConstruction = incon;
+  }
   /**
    * Constructor
    * 
@@ -190,13 +214,7 @@ public class FeatureSettings extends JPanel
     this.af = alignFrame;
     fr = af.getFeatureRenderer();
 
-    // save transparency for restore on Cancel
-    originalTransparency = fr.getTransparency();
-    int originalTransparencyAsPercent = (int) (originalTransparency * 100);
-    transparency.setMaximum(100 - originalTransparencyAsPercent);
-
-    originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
-    originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
+    storeOriginalSettings();
 
     try
     {
@@ -356,7 +374,6 @@ public class FeatureSettings extends JPanel
     }
 
     discoverAllFeatureData();
-    final PropertyChangeListener change;
     final FeatureSettings fs = this;
     fr.addPropertyChangeListener(change = new PropertyChangeListener()
     {
@@ -373,37 +390,90 @@ public class FeatureSettings extends JPanel
       }
 
     });
-
-    frame = new JInternalFrame();
-    frame.setContentPane(this);
-    if (Platform.isAMac())
+    SplitContainerI splitframe = af.getSplitViewContainer();
+    if (splitframe != null)
     {
-      Desktop.addInternalFrame(frame,
-              MessageManager.getString("label.sequence_feature_settings"),
-              600, 480);
+      frame = null; // keeps eclipse happy
+      splitframe.addFeatureSettingsUI(this);
     }
     else
     {
-      Desktop.addInternalFrame(frame,
-              MessageManager.getString("label.sequence_feature_settings"),
-              600, 450);
-    }
-    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+      frame = new JInternalFrame();
+      frame.setContentPane(this);
+      Rectangle bounds = af.getFeatureSettingsGeometry();
+      String title;
+      if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
+              af.alignPanel.av.getSequenceSetId()).length > 1)
+      {
+        title = MessageManager.formatMessage(
+                "label.sequence_feature_settings_for_view",
+                af.alignPanel.getViewName());
+      }
+      else
+      {
+        title = MessageManager.getString("label.sequence_feature_settings");
+      }
+      if (bounds == null)
+      {
+        if (Platform.isAMac())
+        {
+          Desktop.addInternalFrame(frame, title, 600, 480);
+        }
+        else
+        {
+          Desktop.addInternalFrame(frame, title, 600, 450);
+        }
+      }
+      else
+      {
+        Desktop.addInternalFrame(frame, title,
+                false, bounds.width, bounds.height);
+        frame.setBounds(bounds);
+        frame.setVisible(true);
+      }
+      frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
 
-    frame.addInternalFrameListener(
-            new javax.swing.event.InternalFrameAdapter()
-            {
-              @Override
-              public void internalFrameClosed(
-                      javax.swing.event.InternalFrameEvent evt)
+      frame.addInternalFrameListener(
+              new javax.swing.event.InternalFrameAdapter()
               {
-                fr.removePropertyChangeListener(change);
-              };
-            });
-    frame.setLayer(JLayeredPane.PALETTE_LAYER);
+                @Override
+                public void internalFrameClosed(
+                        javax.swing.event.InternalFrameEvent evt)
+                {
+                  featureSettings_isClosed();
+                };
+              });
+      frame.setLayer(JLayeredPane.PALETTE_LAYER);
+    }
     inConstruction = false;
   }
 
+  PropertyChangeListener change;
+
+  private JCheckBox showComplementOnTop;
+
+  private AbstractButton showComplement;
+
+  private void updateComplementButtons()
+  {
+    showComplement.setSelected(af.getViewport().isShowComplementFeatures());
+    showComplementOnTop
+            .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
+  }
+
+  @Override
+  public AlignViewControllerGuiI getAlignframe()
+  {
+    return af;
+  }
+
+  @Override
+  public void featureSettings_isClosed()
+  {
+    fr.removePropertyChangeListener(change);
+    change = null;
+  }
+
   protected void popupSort(final int rowSelected, final String type,
           final Object typeCol, final Map<String, float[][]> minmax, int x,
           int y)
@@ -1079,11 +1149,38 @@ public class FeatureSettings extends JPanel
     table.repaint();
   }
 
+  /**
+   * close ourselves but leave any existing UI handlers (e.g a CDS/Protein tabbed
+   * feature settings dialog) intact
+   */
+  public void closeOldSettings()
+  {
+    closeDialog(false);
+  }
+
+  /**
+   * close the feature settings dialog (and any containing frame)
+   */
   public void close()
   {
+    closeDialog(true);
+  }
+
+  private void closeDialog(boolean closeContainingFrame)
+  {
     try
     {
-      frame.setClosed(true);
+      if (frame != null)
+      {
+        af.setFeatureSettingsGeometry(frame.getBounds());
+        frame.setClosed(true);
+      }
+      else
+      {
+        SplitContainerI sc = af.getSplitViewContainer();
+        sc.closeFeatureSettings(this, closeContainingFrame);
+        af.featureSettings = null;
+      }
     } catch (Exception exe)
     {
     }
@@ -1134,6 +1231,9 @@ public class FeatureSettings extends JPanel
   {
     this.setLayout(new BorderLayout());
 
+    final boolean hasComplement = af.getViewport()
+            .getCodingComplement() != null;
+
     JPanel settingsPane = new JPanel();
     settingsPane.setLayout(new BorderLayout());
 
@@ -1206,30 +1306,48 @@ public class FeatureSettings extends JPanel
         }
       }
     });
-
-    JButton cancel = new JButton(MessageManager.getString("action.cancel"));
+    // Cancel for a SplitFrame should just revert changes to the currently displayed
+    // settings. May want to do this for either or both - so need a splitview
+    // feature settings cancel/OK.
+    JButton cancel = new JButton(MessageManager
+            .getString(hasComplement ? "action.revert" : "action.cancel"));
+    cancel.setToolTipText(MessageManager.getString(hasComplement
+            ? "action.undo_changes_to_feature_settings"
+            : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
     cancel.setFont(JvSwingUtils.getLabelFont());
+    // TODO: disable cancel (and apply!) until current settings are different
     cancel.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        fr.setTransparency(originalTransparency);
-        fr.setFeatureFilters(originalFilters);
-        updateFeatureRenderer(originalData);
-        af.getViewport().setViewStyle(originalViewStyle);
-        close();
+        revert();
+        refreshDisplay();
+        if (!hasComplement)
+        {
+          close();
+        }
       }
     });
-
-    JButton ok = new JButton(MessageManager.getString("action.ok"));
+    // Cancel for the whole dialog should cancel both CDS and Protein.
+    // OK for an individual feature settings just applies changes, but dialog
+    // remains open
+    JButton ok = new JButton(MessageManager
+            .getString(hasComplement ? "action.apply" : "action.ok"));
     ok.setFont(JvSwingUtils.getLabelFont());
     ok.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        close();
+        if (!hasComplement)
+        {
+          close();
+        }
+        else
+        {
+          storeOriginalSettings();
+        }
       }
     });
 
@@ -1283,8 +1401,7 @@ public class FeatureSettings extends JPanel
                     ? MessageManager.getString("label.protein")
                             .toLowerCase()
                     : "CDS");
-    JCheckBox showComplement = new JCheckBox(text);
-    showComplement.setSelected(af.getViewport().isShowComplementFeatures());
+    showComplement = new JCheckBox(text);
     showComplement.addActionListener(new ActionListener()
     {
       @Override
@@ -1296,10 +1413,8 @@ public class FeatureSettings extends JPanel
       }
     });
 
-    JCheckBox showComplementOnTop = new JCheckBox(
+    showComplementOnTop = new JCheckBox(
             MessageManager.getString("label.on_top"));
-    showComplementOnTop
-            .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
     showComplementOnTop.addActionListener(new ActionListener()
     {
       @Override
@@ -1310,7 +1425,38 @@ public class FeatureSettings extends JPanel
         refreshDisplay();
       }
     });
-
+    // JButton viewComplementSettings = new JButton(MessageManager
+    // .formatMessage("label.show_linked_feature_settings",
+    // nucleotide
+    // ? MessageManager.getString("label.protein")
+    // .toLowerCase()
+    // : "CDS"));
+    // viewComplementSettings.addActionListener(new ActionListener()
+    // {
+    //
+    // @Override
+    // public void actionPerformed(ActionEvent e)
+    // {
+    // AlignViewControllerGuiI complAf = af.getSplitViewContainer()
+    // .getComplementAlignFrame(af);
+    // FeatureSettings complFeatureSettings = (FeatureSettings) complAf
+    // .getFeatureSettingsUI();
+    // if (complFeatureSettings != null)
+    // {
+    // complFeatureSettings.frame.setVisible(true);
+    // try
+    // {
+    // complFeatureSettings.frame.setSelected(true);
+    // return;
+    // } catch (Exception q)
+    // {
+    // }
+    // }
+    // {
+    // complAf.showFeatureSettingsUI();
+    // }
+    // }
+    // });
     JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
     bigPanel.add(lowerPanel, BorderLayout.SOUTH);
 
@@ -1321,9 +1467,8 @@ public class FeatureSettings extends JPanel
     transbuttons.add(sortByDens);
     transbuttons.add(help);
 
-    boolean hasComplement = af.getViewport().getCodingComplement() != null;
     JPanel transPanelLeft = new JPanel(
-            new GridLayout(hasComplement ? 3 : 2, 1));
+            new GridLayout(hasComplement ? 4 : 2, 1));
     transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
     transPanelLeft.add(transparency);
     if (hasComplement)
@@ -1903,6 +2048,27 @@ public class FeatureSettings extends JPanel
       return button;
     }
   }
+
+  public boolean isOpen()
+  {
+    if (af.getSplitViewContainer() != null)
+    {
+      return af.getSplitViewContainer().isFeatureSettingsOpen();
+    }
+    return frame != null && !frame.isClosed();
+  }
+
+  @Override
+  public void revert()
+  {
+    fr.setTransparency(originalTransparency);
+    fr.setFeatureFilters(originalFilters);
+    updateFeatureRenderer(originalData);
+    af.getViewport().setViewStyle(originalViewStyle);
+    updateTransparencySliderFromFR();
+    updateComplementButtons();
+    refreshDisplay();
+  }
 }
 
 class FeatureIcon implements Icon
index 25dedc5..1fad9ae 100644 (file)
  */
 package jalview.gui;
 
+import jalview.api.AlignViewControllerGuiI;
+import jalview.api.FeatureSettingsControllerI;
 import jalview.api.SplitContainerI;
+import jalview.controller.FeatureSettingsControllerGuiI;
 import jalview.datamodel.AlignmentI;
 import jalview.jbgui.GAlignFrame;
 import jalview.jbgui.GSplitFrame;
 import jalview.structure.StructureSelectionManager;
+import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.viewmodel.AlignmentViewport;
 
+import java.awt.BorderLayout;
 import java.awt.Component;
+import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
@@ -41,11 +47,17 @@ import java.util.Map.Entry;
 
 import javax.swing.AbstractAction;
 import javax.swing.InputMap;
+import javax.swing.JButton;
 import javax.swing.JComponent;
 import javax.swing.JDesktopPane;
 import javax.swing.JInternalFrame;
+import javax.swing.JLayeredPane;
 import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
 import javax.swing.KeyStroke;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
@@ -75,6 +87,13 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
 
   private static final long serialVersionUID = 1L;
 
+  /**
+   * geometry for Feature Settings Holder
+   */
+  private static final int FS_MIN_WIDTH = 400;
+
+  private static final int FS_MIN_HEIGHT = 400;
+
   public SplitFrame(GAlignFrame top, GAlignFrame bottom)
   {
     super(top, bottom);
@@ -763,6 +782,22 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
             { (AlignFrame) getTopFrame(), (AlignFrame) getBottomFrame() });
   }
 
+  @Override
+  public AlignFrame getComplementAlignFrame(
+          AlignViewControllerGuiI alignFrame)
+  {
+    if (getTopFrame() == alignFrame)
+    {
+      return (AlignFrame) getBottomFrame();
+    }
+    if (getBottomFrame() == alignFrame)
+    {
+      return (AlignFrame) getTopFrame();
+    }
+    // we didn't know anything about this frame...
+    return null;
+  }
+
   /**
    * Replace Cmd-F Find action with our version. This is necessary because the
    * 'default' Finder searches in the first AlignFrame it finds. We need it to
@@ -815,4 +850,259 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     }
     super.setSelected(selected);
   }
-}
+
+  /**
+   * holds the frame for feature settings, so Protein and DNA tabs can be managed
+   */
+  JInternalFrame featureSettingsUI;
+
+  JTabbedPane featureSettingsPanels;
+
+  @Override
+  public void addFeatureSettingsUI(
+          FeatureSettingsControllerGuiI featureSettings)
+  {
+    boolean showInternalFrame = false;
+    if (featureSettingsUI == null || featureSettingsPanels == null)
+    {
+      showInternalFrame = true;
+      featureSettingsPanels = new JTabbedPane();
+      featureSettingsPanels.addChangeListener(new ChangeListener()
+      {
+
+        @Override
+        public void stateChanged(ChangeEvent e)
+        {
+          if (e.getSource() != featureSettingsPanels
+                  || featureSettingsUI == null
+                  || featureSettingsUI.isClosed()
+                  || !featureSettingsUI.isVisible())
+          {
+            // not our tabbed pane
+            return;
+          }
+          int tab = featureSettingsPanels.getSelectedIndex();
+          if (tab < 0 || featureSettingsPanels
+                  .getSelectedComponent() instanceof FeatureSettingsControllerGuiI)
+          {
+            // no tab selected or already showing a feature settings GUI
+            return;
+          }
+          getAlignFrames().get(tab).showFeatureSettingsUI();
+        }
+      });
+      featureSettingsUI = new JInternalFrame(MessageManager.getString(
+              "label.sequence_feature_settings_for_CDS_and_Protein"));
+      featureSettingsPanels.setOpaque(true);
+
+      JPanel dialog = new JPanel();
+      dialog.setOpaque(true);
+      dialog.setLayout(new BorderLayout());
+      dialog.add(featureSettingsPanels, BorderLayout.CENTER);
+      JPanel buttons = new JPanel();
+      JButton ok = new JButton(MessageManager.getString("action.ok"));
+      ok.addActionListener(new ActionListener()
+      {
+
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          try
+          {
+            featureSettingsUI.setClosed(true);
+          } catch (PropertyVetoException pv)
+          {
+            pv.printStackTrace();
+          }
+        }
+      });
+      JButton cancel = new JButton(
+              MessageManager.getString("action.cancel"));
+      cancel.addActionListener(new ActionListener()
+      {
+
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          try
+          {
+            for (Component fspanel : featureSettingsPanels.getComponents())
+            {
+              if (fspanel instanceof FeatureSettingsControllerGuiI)
+              {
+                ((FeatureSettingsControllerGuiI) fspanel).revert();
+              }
+            }
+            featureSettingsUI.setClosed(true);
+          } catch (Exception pv)
+          {
+            pv.printStackTrace();
+          }
+        }
+      });
+      buttons.add(ok);
+      buttons.add(cancel);
+      dialog.add(buttons, BorderLayout.SOUTH);
+      featureSettingsUI.setContentPane(dialog);
+      createDummyTabs();
+    }
+    if (featureSettingsPanels
+            .indexOfTabComponent((Component) featureSettings) > -1)
+    {
+      // just show the feature settings !
+      featureSettingsPanels
+              .setSelectedComponent((Component) featureSettings);
+      return;
+    }
+    // otherwise replace the dummy tab with the given feature settings
+    int pos = getAlignFrames().indexOf(featureSettings.getAlignframe());
+    // if pos==-1 then alignFrame isn't managed by this splitframe
+    if (pos == 0)
+    {
+      featureSettingsPanels.removeTabAt(0);
+      featureSettingsPanels.insertTab(tabName[0], null,
+              (Component) featureSettings,
+              MessageManager.formatMessage(
+                      "label.sequence_feature_settings_for", tabName[0]),
+              0);
+    }
+    if (pos == 1)
+    {
+      featureSettingsPanels.removeTabAt(1);
+      featureSettingsPanels.insertTab(tabName[1], null,
+              (Component) featureSettings,
+              MessageManager.formatMessage(
+                      "label.sequence_feature_settings_for", tabName[1]),
+              1);
+    }
+    featureSettingsPanels.setSelectedComponent((Component) featureSettings);
+
+    // TODO: JAL-3535 - construct a feature settings title including names of
+    // currently selected CDS and Protein names
+
+    if (showInternalFrame)
+    {
+      if (Platform.isAMac())
+      {
+        Desktop.addInternalFrame(featureSettingsUI,
+                MessageManager.getString(
+                        "label.sequence_feature_settings_for_CDS_and_Protein"),
+                600, 480);
+      }
+      else
+      {
+        Desktop.addInternalFrame(featureSettingsUI,
+                MessageManager.getString(
+                        "label.sequence_feature_settings_for_CDS_and_Protein"),
+                600, 450);
+      }
+      featureSettingsUI
+              .setMinimumSize(new Dimension(FS_MIN_WIDTH, FS_MIN_HEIGHT));
+
+      featureSettingsUI.addInternalFrameListener(
+              new javax.swing.event.InternalFrameAdapter()
+              {
+                @Override
+                public void internalFrameClosed(
+                        javax.swing.event.InternalFrameEvent evt)
+                {
+                  for (int tab = 0; tab < featureSettingsPanels
+                          .getTabCount();)
+                  {
+                    FeatureSettingsControllerGuiI fsettings = (FeatureSettingsControllerGuiI) featureSettingsPanels
+                            .getTabComponentAt(tab);
+                    if (fsettings != null)
+                    {
+                      featureSettingsPanels.removeTabAt(tab);
+                      fsettings.featureSettings_isClosed();
+                    }
+                    else
+                    {
+                      tab++;
+                    }
+                  }
+                  featureSettingsPanels = null;
+                  featureSettingsUI = null;
+                };
+              });
+      featureSettingsUI.setLayer(JLayeredPane.PALETTE_LAYER);
+    }
+  }
+
+  /**
+   * tab names for feature settings
+   */
+  private String[] tabName = new String[] {
+      MessageManager.getString("label.CDS"),
+      MessageManager.getString("label.protein") };
+
+  /**
+   * create placeholder tabs which materialise the feature settings for a given
+   * view. Also reinitialises any tabs containing stale feature settings
+   */
+  private void createDummyTabs()
+  {
+    for (int tabIndex = 0; tabIndex < 2; tabIndex++)
+    {
+      JPanel dummyTab = new JPanel();
+      featureSettingsPanels.addTab(tabName[tabIndex], dummyTab);
+    }
+  }
+
+  private void replaceWithDummyTab(FeatureSettingsControllerI toClose)
+  {
+    Component dummyTab = null;
+    for (int tabIndex = 0; tabIndex < 2; tabIndex++)
+    {
+      if (featureSettingsPanels.getTabCount() > tabIndex)
+      {
+        dummyTab = featureSettingsPanels.getTabComponentAt(tabIndex);
+        if (dummyTab instanceof FeatureSettingsControllerGuiI
+                && !dummyTab.isVisible())
+        {
+          featureSettingsPanels.removeTabAt(tabIndex);
+          // close the feature Settings tab
+          ((FeatureSettingsControllerGuiI) dummyTab)
+                  .featureSettings_isClosed();
+          // create a dummy tab in its place
+          dummyTab = new JPanel();
+          featureSettingsPanels.insertTab(tabName[tabIndex], null, dummyTab,
+                  MessageManager.formatMessage(
+                          "label.sequence_feature_settings_for",
+                          tabName[tabIndex]),
+                  tabIndex);
+        }
+      }
+    }
+  }
+
+  @Override
+  public void closeFeatureSettings(
+          FeatureSettingsControllerI featureSettings,
+          boolean closeContainingFrame)
+  {
+    if (featureSettingsUI != null)
+    {
+      if (closeContainingFrame)
+      {
+        try
+        {
+          featureSettingsUI.setClosed(true);
+        } catch (Exception x)
+        {
+        }
+        featureSettingsUI = null;
+      }
+      else
+      {
+        replaceWithDummyTab(featureSettings);
+      }
+    }
+  }
+
+  @Override
+  public boolean isFeatureSettingsOpen()
+  {
+    return featureSettingsUI != null && !featureSettingsUI.isClosed();
+  }
+}
\ No newline at end of file
index 3494181..25e10c3 100644 (file)
@@ -92,12 +92,26 @@ public class MessageManager
 
   public static String formatMessage(String key, Object... params)
   {
-    return MessageFormat.format(rb.getString(key), params);
+    try
+    {
+      return MessageFormat.format(rb.getString(key), params);
+    } catch (Exception e)
+    {
+      log.warning("I18N missing: " + loc + "\t" + key);
+      // e.printStackTrace();
+
+    }
+    String value = "[missing key] " + key + "";
+    for (Object p : params)
+    {
+      value += " '" + p.toString() + "'";
+    }
+    return value;
   }
 
   public static String formatMessage(String key, String[] params)
   {
-    return MessageFormat.format(rb.getString(key), (Object[]) params);
+    return formatMessage(key, (Object[]) params);
   }
 
   /**