debugging selection message send/receive
[jalview.git] / src / jalview / gui / VamsasApplication.java
index 121a200..16da5db 100644 (file)
@@ -1,11 +1,31 @@
-/**
- *
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
+ * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 package jalview.gui;
 
 import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.io.VamsasAppDatastore;
+import jalview.structure.SelectionListener;
+import jalview.structure.SelectionSource;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasListener;
 
@@ -13,8 +33,10 @@ import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.io.File;
 import java.io.IOException;
+import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 
 import javax.swing.JInternalFrame;
 import javax.swing.JOptionPane;
@@ -30,13 +52,17 @@ import uk.ac.vamsas.client.picking.IMessageHandler;
 import uk.ac.vamsas.client.picking.IPickManager;
 import uk.ac.vamsas.client.picking.Message;
 import uk.ac.vamsas.client.picking.MouseOverMessage;
+import uk.ac.vamsas.client.picking.SelectionMessage;
 import uk.ac.vamsas.objects.core.Entry;
+import uk.ac.vamsas.objects.core.Input;
+import uk.ac.vamsas.objects.core.Pos;
+import uk.ac.vamsas.objects.core.Seg;
 
 /**
  * @author jimp
  * 
  */
-public class VamsasApplication
+public class VamsasApplication implements SelectionSource
 {
   IClient vclient = null;
 
@@ -164,25 +190,31 @@ public class VamsasApplication
 
   private void setVclientConfig()
   {
-    if (vclient==null)
+    if (vclient == null)
     {
       return;
     }
-    try {
+    try
+    {
       if (vclient instanceof uk.ac.vamsas.client.simpleclient.SimpleClient)
       {
-        uk.ac.vamsas.client.simpleclient.SimpleClientConfig cfg = ((uk.ac.vamsas.client.simpleclient.SimpleClient)  vclient).getSimpleClientConfig();
+        uk.ac.vamsas.client.simpleclient.SimpleClientConfig cfg = ((uk.ac.vamsas.client.simpleclient.SimpleClient) vclient)
+                .getSimpleClientConfig();
         cfg._validatemergedroots = false;
-        cfg._validateupdatedroots= true; //we may write rubbish otherwise. 
+        cfg._validateupdatedroots = true; // we may write rubbish otherwise.
       }
-    }
-    catch (Error e)
+    } catch (Error e)
     {
-      Cache.log.warn("Probable SERIOUS VAMSAS client incompatibility - carrying on regardless",e);
-    }
-    catch (Exception e)
+      Cache.log
+              .warn(
+                      "Probable SERIOUS VAMSAS client incompatibility - carrying on regardless",
+                      e);
+    } catch (Exception e)
     {
-      Cache.log.warn("Probable VAMSAS client incompatibility - carrying on regardless",e);
+      Cache.log
+              .warn(
+                      "Probable VAMSAS client incompatibility - carrying on regardless",
+                      e);
     }
   }
 
@@ -223,7 +255,7 @@ public class VamsasApplication
     Cache.log
             .debug("Jalview loading the Vamsas Session for the first time.");
     dealWithDocumentUpdate(false); // we don't push an update out to the
-                                    // document yet.
+    // document yet.
     Cache.log.debug("... finished update for the first time.");
   }
 
@@ -263,17 +295,18 @@ public class VamsasApplication
 
   public void push_update()
   {
-    Thread udthread = new Thread(new Runnable() {
+    Thread udthread = new Thread(new Runnable()
+    {
 
       public void run()
       {
         Cache.log.info("Jalview updating to the Vamsas Session.");
-    
+
         dealWithDocumentUpdate(true);
         /*
          * IClientDocument cdoc=null; try { cdoc = vclient.getClientDocument(); }
-         * catch (Exception e) { Cache.log.error("Failed to get client document for
-         * update."); // RAISE A WARNING DIALOG disableGui(false); return; }
+         * catch (Exception e) { Cache.log.error("Failed to get client document
+         * for update."); // RAISE A WARNING DIALOG disableGui(false); return; }
          * updateVamsasDocument(cdoc); updateJalviewGui();
          * cdoc.setVamsasRoots(cdoc.getVamsasRoots()); // propagate update flags
          * back vclient.updateDocument(cdoc);
@@ -281,7 +314,7 @@ public class VamsasApplication
         Cache.log.info("Jalview finished updating to the Vamsas Session.");
         // TODO Auto-generated method stub
       }
-      
+
     });
     udthread.start();
   }
@@ -321,11 +354,32 @@ public class VamsasApplication
     ensureJvVamsas();
     VamsasAppDatastore vds = new VamsasAppDatastore(cdoc, vobj2jv, jv2vobj,
             baseProvEntry(), alRedoState);
-    vds.updateToJalview();
+    try
+    {
+      vds.updateToJalview();
+    } catch (Exception e)
+    {
+      Cache.log.error("Failed to update Jalview from vamsas document.", e);
+    }
+    try
+    {
+      if (firstUpdate)
+      {
+        vds.updateJalviewFromAppdata();
+        // Comment this out to repeatedly read in data from JalviewAppData
+        // firstUpdate=false;
+      }
+    } catch (Exception e)
+    {
+      Cache.log.error(
+              "Exception when updating Jalview settings from Appdata.", e);
+    }
     Cache.log.debug(".. finished updating from sesion document.");
 
   }
 
+  boolean firstUpdate = false;
+
   private void ensureJvVamsas()
   {
     if (jv2vobj == null)
@@ -333,6 +387,7 @@ public class VamsasApplication
       jv2vobj = new IdentityHashMap();
       vobj2jv = new Hashtable();
       alRedoState = new Hashtable();
+      firstUpdate = true;
     }
   }
 
@@ -342,10 +397,25 @@ public class VamsasApplication
   IdentityHashMap jv2vobj = null;
 
   Hashtable vobj2jv = null;
+
   Hashtable alRedoState = null;
+
+  boolean errorsDuringUpdate = false;
+
+  boolean errorsDuringAppUpdate = false;
+
+  /**
+   * update the document accessed through doc. A backup of the current object
+   * bindings is made.
+   * 
+   * @param doc
+   */
   public void updateVamsasDocument(IClientDocument doc)
   {
     ensureJvVamsas();
+    errorsDuringUpdate = false;
+    errorsDuringAppUpdate = false;
+    backup_objectMapping();
     VamsasAppDatastore vds = new VamsasAppDatastore(doc, vobj2jv, jv2vobj,
             baseProvEntry(), alRedoState);
     // wander through frames
@@ -355,6 +425,8 @@ public class VamsasApplication
     {
       return;
     }
+    Hashtable skipList = new Hashtable();
+    Hashtable viewset = new Hashtable();
 
     try
     {
@@ -364,26 +436,75 @@ public class VamsasApplication
         if (frames[i] instanceof AlignFrame)
         {
           AlignFrame af = (AlignFrame) frames[i];
-
-          // update alignment and root from frame.
-          vds.storeVAMSAS(af.getViewport(), af.getTitle());
+          if (!viewset.containsKey(af.getViewport().getSequenceSetId()))
+          {
+            // update alignment and root from frame.
+            boolean stored = false;
+            try
+            {
+              stored = vds.storeVAMSAS(af.getViewport(), af.getTitle());
+            } catch (Exception e)
+            {
+              errorsDuringUpdate = true;
+              Cache.log.error("Exception synchronizing "
+                      + af.getTitle()
+                      + " "
+                      + (af.getViewport().viewName == null ? "" : " view "
+                              + af.getViewport().viewName)
+                      + " to document.", e);
+              stored = false;
+            }
+            if (!stored)
+            { // record skip in skipList
+              skipList.put(af.getViewport().getSequenceSetId(), af);
+            }
+            else
+            {
+              // could try to eliminate sequenceSetId from skiplist ..
+              // (skipList.containsKey(af.getViewport().getSequenceSetId()))
+              // remember sequenceSetId so we can skip all the other views on
+              // same alignment
+              viewset.put(af.getViewport().getSequenceSetId(), af);
+            }
+          }
         }
       }
       // REVERSE ORDER
-      for (int i = frames.length - 1; i > -1; i--)
+      // for (int i = frames.length - 1; i > -1; i--)
+      // {
+      // if (frames[i] instanceof AlignFrame)
+      // {
+      // AlignFrame af = (AlignFrame) frames[i];
+      Iterator aframes = viewset.values().iterator();
+      while (aframes.hasNext())
       {
-        if (frames[i] instanceof AlignFrame)
-        {
-          AlignFrame af = (AlignFrame) frames[i];
+        AlignFrame af = (AlignFrame) aframes.next();
+        // add any AlignedCodonFrame mappings on this alignment to any other.
+        vds.storeSequenceMappings(af.getViewport(), af.getTitle());
+      }
+    } catch (Exception e)
+    {
+      Cache.log.error("Exception synchronizing Views to Document :", e);
+      errorsDuringUpdate = true;
+    }
 
-          // add any AlignedCodonFrame mappings on this alignment to any other.
-          vds.storeSequenceMappings(af.getViewport(), af.getTitle());
-        }
+    try
+    {
+      if (viewset.size() > 0)
+      {
+        // Alignment views were synchronized, so store their state in the
+        // appData, too.
+        // The skipList ensures we don't write out any alignments not actually
+        // in the document.
+        vds.setSkipList(skipList);
+        vds.updateJalviewClientAppdata();
       }
     } catch (Exception e)
     {
-      Cache.log.error("Vamsas Document store exception", e);
+      Cache.log.error("Client Appdata Write exception", e);
+      errorsDuringAppUpdate = true;
     }
+    vds.clearSkipList();
   }
 
   private Entry baseProvEntry()
@@ -459,6 +580,9 @@ public class VamsasApplication
     {
       System.err.println("Exception whilst updating :");
       ee.printStackTrace(System.err);
+      // recover object map backup, since its probably corrupted with references
+      // to Vobjects that don't exist anymore.
+      this.recover_objectMappingBackup();
     }
     Cache.log.debug("Finished updating from document change.");
     disableGui(false);
@@ -482,36 +606,88 @@ public class VamsasApplication
   private void addStoreDocumentHandler()
   {
     final VamsasApplication client = this;
-    vclient.addVorbaEventHandler(uk.ac.vamsas.client.Events.DOCUMENT_REQUESTTOCLOSE, new PropertyChangeListener()
-    {
-      public void propertyChange(PropertyChangeEvent evt)
-      {
-        Cache.log.debug("Asking user if the vamsas session should be stored.");
-        int reply = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
-                "The current VAMSAS session has unsaved data - do you want to save it ?",
-            "VAMSAS Session Shutdown",
-            JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
-        
-            if(reply==JOptionPane.YES_OPTION)
+    vclient.addVorbaEventHandler(
+            uk.ac.vamsas.client.Events.DOCUMENT_REQUESTTOCLOSE,
+            new PropertyChangeListener()
             {
-              Cache.log.debug("Prompting for vamsas store filename.");
-              Desktop.instance.vamsasSave_actionPerformed(null);
-              Cache.log.debug("Finished attempt at storing document.");
-            } 
-            Cache.log.debug("finished dealing with REQUESTTOCLOSE event.");
-      }
-    });
+              public void propertyChange(PropertyChangeEvent evt)
+              {
+                Cache.log
+                        .debug("Asking user if the vamsas session should be stored.");
+                int reply = JOptionPane
+                        .showInternalConfirmDialog(
+                                Desktop.desktop,
+                                "The current VAMSAS session has unsaved data - do you want to save it ?",
+                                "VAMSAS Session Shutdown",
+                                JOptionPane.YES_NO_OPTION,
+                                JOptionPane.QUESTION_MESSAGE);
+
+                if (reply == JOptionPane.YES_OPTION)
+                {
+                  Cache.log.debug("Prompting for vamsas store filename.");
+                  Desktop.instance.vamsasSave_actionPerformed(null);
+                  Cache.log.debug("Finished attempt at storing document.");
+                }
+                Cache.log
+                        .debug("finished dealing with REQUESTTOCLOSE event.");
+              }
+            });
     Cache.log.debug("Added Jalview handler for vamsas document updates.");
   }
+
   public void disableGui(boolean b)
   {
     Desktop.instance.setVamsasUpdate(b);
   }
 
+  Hashtable _backup_vobj2jv;
+
+  IdentityHashMap _backup_jv2vobj;
+
+  /**
+   * make a backup of the object mappings (vobj2jv and jv2vobj)
+   */
+  public void backup_objectMapping()
+  {
+    _backup_vobj2jv = new Hashtable(vobj2jv);
+    _backup_jv2vobj = new IdentityHashMap(jv2vobj);
+  }
+
+  /**
+   * recover original object mappings from the object mapping backup if document
+   * IO failed
+   * 
+   * @throws Error
+   *                 if backup_objectMapping was not called.
+   */
+  public void recover_objectMappingBackup()
+  {
+    if (_backup_vobj2jv == null)
+    {
+      throw new Error(
+              "IMPLEMENTATION ERROR: Cannot recover vamsas object mappings - no backup was made.");
+    }
+    jv2vobj.clear();
+    Iterator el = _backup_jv2vobj.entrySet().iterator();
+    while (el.hasNext())
+    {
+      java.util.Map.Entry mp = (java.util.Map.Entry) el.next();
+      jv2vobj.put(mp.getKey(), mp.getValue());
+    }
+    el = _backup_vobj2jv.entrySet().iterator();
+    while (el.hasNext())
+    {
+      java.util.Map.Entry mp = (java.util.Map.Entry) el.next();
+      vobj2jv.put(mp.getKey(), mp.getValue());
+    }
+  }
+
   private boolean joinedSession = false;
 
   private VamsasListener picker = null;
 
+  private SelectionListener selecter;
+
   private void startSession()
   {
     if (inSession())
@@ -531,13 +707,19 @@ public class VamsasApplication
         final IPickManager pm = vclient.getPickManager();
         final StructureSelectionManager ssm = StructureSelectionManager
                 .getStructureSelectionManager();
+        final SelectionSource me = this;
         pm.registerMessageHandler(new IMessageHandler()
         {
           String last = null;
 
           public void handleMessage(Message message)
           {
-            if (message instanceof MouseOverMessage && vobj2jv != null)
+            if (vobj2jv == null)
+            {
+              // we are not in a session yet.
+              return;
+            }
+            if (message instanceof MouseOverMessage)
             {
               MouseOverMessage mm = (MouseOverMessage) message;
               String mstring = mm.getVorbaID() + " " + mm.getPosition();
@@ -561,6 +743,161 @@ public class VamsasApplication
                         .getPosition());
               }
             }
+            if (message instanceof uk.ac.vamsas.client.picking.SelectionMessage)
+            {
+              // we only care about AlignmentSequence selections
+              SelectionMessage sm = (SelectionMessage) message;
+              sm.validate();
+              System.err.println("Received\n"+sm.getRawMessage());
+              Object[] jvobjs = sm.getVorbaIDs()==null ? null : new Object[sm.getVorbaIDs().length];
+              if (jvobjs==null)
+              {
+                // TODO: rationalise : can only clear a selection over a referred to object
+                ssm.sendSelection(null,null,me);
+                return;
+              }
+              Class type = null;
+              boolean send = true;
+              for (int o = 0; o < jvobjs.length; o++)
+              {
+                jvobjs[o] = vobj2jv.get(sm.getVorbaIDs()[o]);
+                if (jvobjs[o] == null)
+                {
+                  // can't cope with selections for unmapped objects
+                  //continue;
+                }
+                if (type == null)
+                {
+                  type = jvobjs[o].getClass();
+                }
+                ;
+                if (type != jvobjs[o].getClass())
+                {
+                  send=false;
+                  // discard - can't cope with selections over mixed objects
+                  //continue;
+                }
+              }
+              SequenceGroup jselection = null;
+              ColumnSelection colsel = null;
+              if (type == jalview.datamodel.Alignment.class)
+              {
+                if (jvobjs.length == 1)
+                {
+                  // TODO if (sm.isNone())// send a message to select the specified columns over the
+                  // given
+                  // alignment
+
+                  send = true;
+                }
+              }
+              if (type == jalview.datamodel.Sequence.class)
+              {
+
+                SequenceI seq;
+                boolean aligned = ((jalview.datamodel.Sequence) jvobjs[0])
+                        .getDatasetSequence() != null;
+                int maxWidth=0;
+                if (aligned)
+                {
+                  jselection = new SequenceGroup();
+                  jselection.addSequence(seq =
+                        (jalview.datamodel.Sequence) jvobjs[0], false);
+                  maxWidth = seq.getLength();
+                }
+                for (int c = 1; aligned && jvobjs.length > 1 && c < jvobjs.length; c++)
+                {
+                  if (((jalview.datamodel.Sequence) jvobjs[c])
+                          .getDatasetSequence() == null)
+                  {
+                    aligned = false;
+                    continue;
+                  }
+                  else
+                  {
+                    jselection.addSequence(
+                            seq = (jalview.datamodel.Sequence) jvobjs[c], false);
+                    if (maxWidth<seq.getLength())
+                    {
+                      maxWidth = seq.getLength();
+                    }
+                    
+                  }
+                }
+                if (!aligned)
+                {
+                  jselection = null;
+                  // if cardinality is greater than one then verify all
+                  // sequences are alignment sequences.
+                  if (jvobjs.length == 1)
+                  {
+                    // find all instances of this dataset sequence in the
+                    // displayed alignments containing the associated range and
+                    // select them.
+                  }
+                }
+                else
+                {
+                  jselection.setStartRes(0);
+                  jselection.setEndRes(maxWidth);
+                  // locate the alignment containing the given sequences and
+                  // select the associated ranges on them.
+                  if (sm.getRanges() != null)
+                  {
+                    int[] prange = uk.ac.vamsas.objects.utils.Range.getBounds(sm.getRanges());
+                    boolean rangeset=false;
+                    colsel = new ColumnSelection();
+                    prange = uk.ac.vamsas.objects.utils.Range.getIntervals(sm.getRanges());
+                    for (int p=0;p<prange.length;p+=2)
+                    {
+                      int d = (prange[p]<=prange[p+1]) ? 1 : -1;
+                      if (!rangeset)
+                      {
+                        if (jselection!=null)
+                        {
+                          // set the bounds of the selected area using the first interval.
+                          jselection.setStartRes(prange[p]-1);
+                          jselection.setEndRes(prange[p+1]-1);
+                          rangeset=true;
+                        }
+                      } else {
+                        // try to join up adjacent columns to make a larger selection
+                        // lower and upper bounds
+                        int l=(d<0) ? 1 : 0;
+                        int u=(d>0) ? 1 : 0;
+                        
+                        if (jselection.getStartRes()>0 && prange[p+l]==jselection.getStartRes())
+                        {
+                          jselection.setStartRes(prange[p+l]-1);
+                        }
+                        if (jselection.getEndRes()<=maxWidth && prange[p+u]==(jselection.getEndRes()+2))
+                        {
+                          jselection.setEndRes(prange[p+u]-1);
+                        }
+                      }
+                      // mark all the columns in the range.
+                      for (int sr=prange[p],er=prange[p+1],de=er+d; sr!=de; sr+=d)
+                      {
+                        colsel.addElement(sr-1);
+                      }
+                    }
+                  }
+                  send = true;
+                }
+              }
+              if (send)
+              {
+                ssm.sendSelection(jselection, colsel, me);
+              }
+              // discard message.
+              for (int c = 0; c < jvobjs.length; c++)
+              {
+                jvobjs[c] = null;
+              }
+              ;
+              jvobjs = null;
+              return;
+            }
           }
         });
         picker = new VamsasListener()
@@ -589,7 +926,100 @@ public class VamsasApplication
             }
           }
         };
+        selecter = new SelectionListener()
+        {
+
+          public void selection(SequenceGroup seqsel,
+                  ColumnSelection colsel, SelectionSource source)
+          {
+            if (vobj2jv==null)
+            {
+              Cache.log.warn("Selection listener still active for dead session.");
+              // not in a session.
+              return;
+            }
+            if (source != me)
+            {
+              AlignmentI visal=null;
+              if (source instanceof AlignViewport)
+              {
+                visal = ((AlignViewport) source).getAlignment();
+              }
+              SelectionMessage sm = null;
+              if ((seqsel == null || seqsel.getSize() == 0)
+                      && (colsel == null || colsel.getSelected() == null || colsel
+                              .getSelected().size() == 0))
+              {
+                if (source instanceof AlignViewport)
+                {
+                  // the empty selection.
+                  sm = new SelectionMessage("jalview", new String[] { ((AlignViewport)source).getSequenceSetId()}, null, true);
+                } else {
+                  // the empty selection.
+                  sm = new SelectionMessage("jalview", null, null, true);
+                }
+              }
+              else
+              {
+                String[] vobj = new String[seqsel.getSize()];
+                int o = 0;
+                Enumeration sels = seqsel.getSequences(null).elements();
+                while (sels.hasMoreElements())
+                {
+                  SequenceI sel = (SequenceI) sels.nextElement();
+                  VorbaId v = (VorbaId) jv2vobj.get(sel);
+                  if (v != null)
+                  {
+                    vobj[o++] = v.toString();
+                  }
+                }
+                if (o < vobj.length)
+                {
+                  String t[] = vobj;
+                  vobj = new String[o];
+                  System.arraycopy(t, 0, vobj, 0, o);
+                  t = null;
+                }
+                Input range = null;
+                if (seqsel!=null && colsel != null)
+                {
+                  // deparse the colsel into positions on the vamsas alignment
+                  // sequences
+                  range = new Input();
+                  if (colsel.getSelected()!=null && colsel.getSelected().size()>0 && visal!=null && seqsel.getSize()==visal.getHeight())
+                  {
+                    // gather selected columns outwith the sequence positions too
+                    Enumeration cols = colsel.getSelected().elements();
+                    while (cols.hasMoreElements())
+                    {
+                      int ival = ((Integer) cols.nextElement()).intValue();
+                      Pos p = new Pos();
+                      p.setI(ival+1);
+                      range.addPos(p);
+                    }
+                  } else {
+                    int[] intervals = colsel.getVisibleContigs(seqsel.getStartRes(), seqsel.getEndRes()+1);
+                    for (int iv=0;iv<intervals.length; iv+=2)
+                    {
+                      Seg s = new Seg();
+                      s.setStart(intervals[iv]+1); // vamsas indices begin at 1, not zero.
+                      s.setEnd(intervals[iv+1]+1);
+                      s.setInclusive(true);
+                      range.addSeg(s);
+                    }
+                  }
+                }
+                sm = new SelectionMessage("jalview", vobj, range);
+              }
+              sm.validate(); // debug
+              Cache.log.debug("Selection Message\n"+sm.getRawMessage());
+              pm.sendMessage(sm);
+            }
+          }
+
+        };
         ssm.addStructureViewerListener(picker); // better method here
+        ssm.addSelectionListener(selecter);
       } catch (Exception e)
       {
         Cache.log.error("Failed to init Vamsas Picking", e);