pickmanager on sequence and alignment position, vamsas pick broadcast and refactor...
[jalview.git] / src / jalview / gui / FeatureSettings.java
index 5e4f540..de61be3 100755 (executable)
@@ -23,6 +23,9 @@ import java.util.*;
 
 import java.awt.*;
 import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
 import javax.swing.*;
 import javax.swing.event.*;
 import javax.swing.table.*;
@@ -34,7 +37,7 @@ public class FeatureSettings
     extends JPanel
 {
   DasSourceBrowser dassourceBrowser;
-  jalview.io.DasSequenceFeatureFetcher dasFeatureFetcher;
+  jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher;
   JPanel settingsPane = new JPanel();
   JPanel dasSettingsPane = new JPanel();
 
@@ -118,29 +121,57 @@ public class FeatureSettings
 
     if (af.getViewport().featuresDisplayed == null || fr.renderOrder == null)
     {
-      fr.findAllFeatures();
+      fr.findAllFeatures(true); // display everything!
     }
 
     setTableData();
+    final PropertyChangeListener change;
+    final FeatureSettings fs=this;
+    fr.addPropertyChangeListener(change=new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent evt)
+      {
+        if (!fs.resettingTable && !fs.handlingUpdate) {
+          fs.handlingUpdate=true;
+          fs.resetTable(null); // new groups may be added with new seuqence feature types only
+          fs.handlingUpdate=false;
+        }
+      }
+
+    });
 
     frame = new JInternalFrame();
     frame.setContentPane(this);
     Desktop.addInternalFrame(frame, "Sequence Feature Settings", 400, 450);
+    frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
+    {
+      public void internalFrameClosed(
+          javax.swing.event.InternalFrameEvent evt)
+      {
+        fr.removePropertyChangeListener(change);
+      }
+      ;
+    });
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
   }
+  /**
+   * true when Feature Settings are updating from feature renderer
+   */
+  private boolean handlingUpdate=false;
 
+  /**
+   * contains a float[3] for each feature type string. created by setTableData
+   */
+  Hashtable typeWidth=null;
   synchronized public void setTableData()
   {
     if (fr.featureGroups == null)
     {
       fr.featureGroups = new Hashtable();
     }
-
     Vector allFeatures = new Vector();
     Vector allGroups = new Vector();
     SequenceFeature[] tmpfeatures;
     String group;
-
     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
     {
       if (af.getViewport().alignment.getSequenceAt(i).getDatasetSequence().
@@ -167,54 +198,10 @@ public class FeatureSettings
           if (!allGroups.contains(group))
           {
             allGroups.addElement(group);
-
-            boolean visible = true;
-            if (fr.featureGroups.containsKey(group))
-            {
-              visible = ( (Boolean) fr.featureGroups.get(group)).booleanValue();
-            }
-
-            if (groupPanel == null)
-            {
-              groupPanel = new JPanel();
-            }
-
-            boolean alreadyAdded = false;
-            for (int g = 0; g < groupPanel.getComponentCount(); g++)
-            {
-              if ( ( (JCheckBox) groupPanel.getComponent(g))
-                  .getText().equals(group))
-              {
-                alreadyAdded = true;
-                break;
-              }
-            }
-
-            if (alreadyAdded)
+            if (group!=null)
             {
-              continue;
+              checkGroupState(group);
             }
-
-            fr.featureGroups.put(group, new Boolean(visible));
-
-            final JCheckBox check = new JCheckBox(group, visible);
-            check.setFont(new Font("Serif", Font.BOLD, 12));
-            check.addItemListener(new ItemListener()
-            {
-              public void itemStateChanged(ItemEvent evt)
-              {
-                fr.featureGroups.put(check.getText(),
-                                     new Boolean(check.isSelected()));
-                af.alignPanel.seqPanel.seqCanvas.repaint();
-                if (af.alignPanel.overviewPanel != null)
-                {
-                  af.alignPanel.overviewPanel.updateOverviewImage();
-                }
-
-                resetTable(true);
-              }
-            });
-            groupPanel.add(check);
           }
         }
 
@@ -226,19 +213,87 @@ public class FeatureSettings
       }
     }
 
-    resetTable(false);
+    resetTable(null);
 
     validate();
   }
+  /**
+   *
+   * @param group
+   * @return true if group has been seen before and is already added to set.
+   */
+  private boolean checkGroupState(String group) {
+    boolean visible;
+    if (fr.featureGroups.containsKey(group))
+    {
+      visible = ( (Boolean) fr.featureGroups.get(group)).booleanValue();
+        } else {
+        visible=true; // new group is always made visible
+      }
+
+      if (groupPanel == null)
+      {
+        groupPanel = new JPanel();
+      }
+
+      boolean alreadyAdded = false;
+      for (int g = 0; g < groupPanel.getComponentCount(); g++)
+      {
+        if ( ( (JCheckBox) groupPanel.getComponent(g))
+            .getText().equals(group))
+        {
+          alreadyAdded = true;
+          ((JCheckBox)groupPanel.getComponent(g)).setSelected(visible);
+          break;
+        }
+      }
 
-  void resetTable(boolean groupsChanged)
+      if (alreadyAdded)
+      {
+
+        return true;
+      }
+
+      fr.featureGroups.put(group, new Boolean(visible));
+      final String grp = group;
+      final JCheckBox check = new JCheckBox(group, visible);
+      check.setFont(new Font("Serif", Font.BOLD, 12));
+      check.addItemListener(new ItemListener()
+      {
+        public void itemStateChanged(ItemEvent evt)
+        {
+          fr.featureGroups.put(check.getText(),
+                               new Boolean(check.isSelected()));
+          af.alignPanel.seqPanel.seqCanvas.repaint();
+          if (af.alignPanel.overviewPanel != null)
+          {
+            af.alignPanel.overviewPanel.updateOverviewImage();
+          }
+
+          resetTable(new String[] { grp } );
+        }
+      });
+      groupPanel.add(check);
+      return false;
+  }
+  boolean resettingTable=false;
+  synchronized void resetTable(String[] groupChanged)
   {
+    if (resettingTable==true)
+    {
+      return;
+    }
+    resettingTable=true;
+    typeWidth=new Hashtable();
+    // TODO: change avWidth calculation to 'per-sequence' average and use long rather than float
+    float[] avWidth=null;
     SequenceFeature[] tmpfeatures;
     String group = null, type;
     Vector visibleChecks = new Vector();
 
     //Find out which features should be visible depending on which groups
     //are selected / deselected
+    //and recompute average width ordering
     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
     {
 
@@ -263,12 +318,26 @@ public class FeatureSettings
         if (group == null || fr.featureGroups.get(group) == null ||
             ( (Boolean) fr.featureGroups.get(group)).booleanValue())
         {
+          if (group!=null)
+            checkGroupState(group);
           type = tmpfeatures[index].getType();
           if (!visibleChecks.contains(type))
           {
             visibleChecks.addElement(type);
           }
         }
+        if (!typeWidth.containsKey(tmpfeatures[index].getType())) {
+          typeWidth.put(tmpfeatures[index].getType(), avWidth=new float[3]);
+        } else {
+          avWidth = (float[]) typeWidth.get(tmpfeatures[index].getType());
+        }
+        avWidth[0]++;
+        if (tmpfeatures[index].getBegin()>tmpfeatures[index].getEnd())
+        {
+          avWidth[1]+=1+tmpfeatures[index].getBegin()-tmpfeatures[index].getEnd();
+        } else {
+          avWidth[1]+=1+tmpfeatures[index].getEnd()-tmpfeatures[index].getBegin();
+        }
         index++;
       }
     }
@@ -279,6 +348,8 @@ public class FeatureSettings
 
     if (fr.renderOrder != null)
     {
+      if (!handlingUpdate)
+        fr.findAllFeatures(groupChanged!=null); // prod to update colourschemes. but don't affect display
       //First add the checks in the previous render order,
       //in case the window has been closed and reopened
       for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
@@ -292,8 +363,7 @@ public class FeatureSettings
 
         data[dataIndex][0] = type;
         data[dataIndex][1] = fr.getColour(type);
-        data[dataIndex][2] = new Boolean(af.getViewport().featuresDisplayed.
-                                         containsKey(type));
+        data[dataIndex][2] = new Boolean(af.getViewport().featuresDisplayed.containsKey(type));
         dataIndex++;
         visibleChecks.removeElement(type);
       }
@@ -337,8 +407,27 @@ public class FeatureSettings
       bigPanel.add(groupPanel, BorderLayout.NORTH);
     }
 
-    updateFeatureRenderer(data);
-
+    updateFeatureRenderer(data, groupChanged!=null);
+    resettingTable=false;
+  }
+  /**
+   * reorder data based on the featureRenderers global priority list.
+   * @param data
+   */
+  private void ensureOrder(Object[][] data)
+  {
+    boolean sort=false;
+    float[] order = new float[data.length];
+    for (int i=0;i<order.length; i++)
+    {
+      order[i] = fr.getOrder(data[i][0].toString());
+      if (order[i]<0)
+        order[i] = fr.setOrder(data[i][0].toString(), i/order.length);
+      if (i>1)
+        sort = sort || order[i-1]>order[i];
+    }
+    if (sort)
+      jalview.util.QuickSort.sort(order, data);
   }
 
   void load()
@@ -368,15 +457,21 @@ public class FeatureSettings
             JalviewUserColours();
         jucs = (jalview.binding.JalviewUserColours) jucs.unmarshal(in);
 
-        for (int i = 0; i < jucs.getColourCount(); i++)
+        for (int i = jucs.getColourCount()-1; i >=0; i--)
         {
-          fr.setColour(jucs.getColour(i).getName(),
+          String name;
+          fr.setColour(name=jucs.getColour(i).getName(),
                        new Color(Integer.parseInt(jucs.getColour(i).getRGB(),
                                                   16)));
+          fr.setOrder(name,(i==0) ? 0 : i/jucs.getColourCount());
+        }
+        if (table!=null) {
+          resetTable(null);
+          Object[][] data=((FeatureTableModel) table.getModel()).getData();
+          ensureOrder(data);
+          updateFeatureRenderer(data,false);
+          table.repaint();
         }
-
-        setTableData();
-        af.alignPanel.paintAlignment(true);
       }
       catch (Exception ex)
       {
@@ -411,15 +506,24 @@ public class FeatureSettings
             new FileOutputStream(choice), "UTF-8"));
 
         Enumeration e = fr.featureColours.keys();
+        float[] sortOrder = new float[fr.featureColours.size()];
+        String[] sortTypes = new String[fr.featureColours.size()];
+        int i=0;
         while (e.hasMoreElements())
         {
+          sortTypes[i] = e.nextElement().toString();
+          sortOrder[i]  = fr.getOrder(sortTypes[i]);
+          i++;
+        }
+        jalview.util.QuickSort.sort(sortOrder, sortTypes);
+        sortOrder=null;
+        for (i=0; i<sortTypes.length; i++) {
           jalview.binding.Colour col = new jalview.binding.Colour();
-          col.setName(e.nextElement().toString());
+          col.setName(sortTypes[i]);
           col.setRGB(jalview.util.Format.getHexString(
               fr.getColour(col.getName())));
           ucs.addColour(col);
         }
-
         ucs.marshal(out);
         out.close();
       }
@@ -441,7 +545,50 @@ public class FeatureSettings
           i, 2);
     }
   }
+  public void orderByAvWidth() {
+    if (table==null || table.getModel()==null)
+      return;
+    Object[][] data = ((FeatureTableModel) table.getModel()).getData();
+    float[] width = new float[data.length];
+    float[] awidth;
+    float max=0;
+    int num=0;
+    for (int i=0;i<data.length;i++) {
+       awidth = (float[]) typeWidth.get(data[i][0]);
+       if (awidth[0]>0) {
+         width[i] = awidth[1]/awidth[0];// *awidth[0]*awidth[2]; - better weight - but have to make per sequence, too (awidth[2])
+         //if (width[i]==1) // hack to distinguish single width sequences.
+         num++;
+       } else {
+         width[i]=0;
+       }
+       if (max<width[i])
+         max=width[i];
+    }
+    boolean sort=false;
+    for (int i=0;i<width.length; i++) {
+      //awidth = (float[]) typeWidth.get(data[i][0]);
+      if (width[i]==0)
+      {
+        width[i] = fr.getOrder(data[i][0].toString());
+        if (width[i]<0)
+        {
+          width[i] = fr.setOrder(data[i][0].toString(), i/data.length);
+        }
+      } else {
+        width[i] /=max; // normalize
+        fr.setOrder(data[i][0].toString(), width[i]); // store for later
+      }
+      if (i>0)
+        sort = sort || width[i-1]>width[i];
+    }
+    if (sort)
+      jalview.util.QuickSort.sort(width, data);
+    // update global priority order
 
+    updateFeatureRenderer(data,false);
+    table.repaint();
+  }
   public void close()
   {
     try
@@ -455,13 +602,12 @@ public class FeatureSettings
 
   public void updateFeatureRenderer(Object[][] data)
   {
-    fr.setFeaturePriority(data);
+    updateFeatureRenderer(data, true);
+  }
+  private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
+  {
+    fr.setFeaturePriority(data, visibleNew);
     af.alignPanel.paintAlignment(true);
-
-    if (af.alignPanel.overviewPanel != null)
-    {
-      af.alignPanel.overviewPanel.updateOverviewImage();
-    }
   }
 
   int selectedRow = -1;
@@ -481,6 +627,8 @@ public class FeatureSettings
   JButton fetchDAS = new JButton();
   JButton saveDAS = new JButton();
   JButton cancelDAS = new JButton();
+  JButton optimizeOrder = new JButton();
+  JPanel transbuttons = new JPanel(new BorderLayout());
   private void jbInit()
       throws Exception
   {
@@ -497,6 +645,13 @@ public class FeatureSettings
         invertSelection();
       }
     });
+    optimizeOrder.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
+    optimizeOrder.setText("Optimise Order");
+    optimizeOrder.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        orderByAvWidth();
+      }
+    });
     cancel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
     cancel.setText("Cancel");
     cancel.addActionListener(new ActionListener()
@@ -576,7 +731,9 @@ public class FeatureSettings
     tabbedPane.addTab("DAS Settings", dasSettingsPane);
     bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
     transPanel.add(transparency);
-    transPanel.add(invert);
+    transbuttons.add(invert, java.awt.BorderLayout.NORTH);
+    transbuttons.add(optimizeOrder,java.awt.BorderLayout.SOUTH);
+    transPanel.add(transbuttons);
     buttonPanel.add(ok);
     buttonPanel.add(cancel);
     buttonPanel.add(loadColours);
@@ -621,7 +778,7 @@ public class FeatureSettings
     }
 
     dasFeatureFetcher =
-        new jalview.io.DasSequenceFeatureFetcher(
+        new jalview.ws.DasSequenceFeatureFetcher(
             dataset,
             this,
             selectedSources);
@@ -647,6 +804,17 @@ public class FeatureSettings
     fetchDAS.setEnabled(true);
     cancelDAS.setEnabled(false);
   }
+  public void noDasSourceActive()
+  {
+    JOptionPane.showInternalConfirmDialog(Desktop.desktop,
+            "No das sources were selected.\n"
+            + "Please select some sources and\n"
+            +" try again.",
+            "No Sources Selected",
+            JOptionPane.DEFAULT_OPTION,
+            JOptionPane.INFORMATION_MESSAGE);
+    complete();
+  }
 
   /////////////////////////////////////////////////////////////////////////
   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
@@ -761,6 +929,7 @@ public class FeatureSettings
       return this;
     }
   }
+
 }
 
 class ColorEditor