Merge branch 'develop' into JAL-1483_featureBasedTreeCalc
authorJim Procter <jprocter@dundee.ac.uk>
Fri, 14 Nov 2014 12:16:32 +0000 (12:16 +0000)
committerJim Procter <jprocter@dundee.ac.uk>
Fri, 14 Nov 2014 12:16:32 +0000 (12:16 +0000)
Conflicts:
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/ws/AWSThread.java

30 files changed:
1  2 
src/jalview/api/AlignViewControllerI.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AppletJmol.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/SeqPanel.java
src/jalview/bin/JalviewLite.java
src/jalview/controller/AlignViewController.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/AppJmol.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/gui/JalviewChimeraBindingModel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/SeqPanel.java
src/jalview/io/HTMLOutput.java
src/jalview/schemes/AnnotationColourGradient.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/ws/AWSThread.java

@@@ -191,12 -192,12 +192,20 @@@ public interface AlignViewport
  
    void setConservation(Conservation cons);
  
+   /**
+    * get a copy of the currently visible alignment annotation
+    * @param selectedOnly if true - trim to selected regions on the alignment
+    * @return an empty list or new alignment annotation objects shown only visible columns trimmed to selected region only
+    */
+   List<AlignmentAnnotation> getVisibleAlignmentAnnotation(
+           boolean selectedOnly);
 +  FeaturesDisplayedI getFeaturesDisplayed();
 +
 +  String getSequenceSetId();
 +
 +  boolean isShowSequenceFeatures();
 +
 +  void setShowSequenceFeatures(boolean b);
 +
  }
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -95,8 -96,12 +96,10 @@@ public class AlignViewport extends Alig
  
    boolean renderGaps = true;
  
 -  boolean showSequenceFeatures = false;
 -
    boolean showAnnotation = true;
  
+   SequenceAnnotationOrder sortAnnotationsBy = null;
    int charHeight;
  
    int charWidth;
Simple merge
Simple merge
index 0000000,3c53762..75aed5d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1245 +1,1245 @@@
+ /*
+  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+  * Copyright (C) 2014 The Jalview Authors
+  * 
+  * This file is part of Jalview.
+  * 
+  * Jalview 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 3
+  * of the License, or (at your option) any later version.
+  *  
+  * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+  * The Jalview Authors are detailed in the 'AUTHORS' file.
+  */
+ package jalview.gui;
+ import jalview.api.SequenceStructureBinding;
+ import jalview.api.structures.JalviewStructureDisplayI;
+ import jalview.bin.Cache;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
+ import jalview.gui.ViewSelectionMenu.ViewSetProvider;
+ import jalview.io.AppletFormatAdapter;
+ import jalview.io.JalviewFileChooser;
+ import jalview.io.JalviewFileView;
+ import jalview.jbgui.GStructureViewer;
+ import jalview.schemes.BuriedColourScheme;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.HelixColourScheme;
+ import jalview.schemes.HydrophobicColourScheme;
+ import jalview.schemes.PurinePyrimidineColourScheme;
+ import jalview.schemes.StrandColourScheme;
+ import jalview.schemes.TaylorColourScheme;
+ import jalview.schemes.TurnColourScheme;
+ import jalview.schemes.ZappoColourScheme;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
+ import jalview.ws.dbsources.Pdb;
+ import java.awt.Component;
+ import java.awt.event.ActionEvent;
+ import java.awt.event.ActionListener;
+ import java.awt.event.ItemEvent;
+ import java.awt.event.ItemListener;
+ import java.io.BufferedReader;
+ import java.io.File;
+ import java.io.FileOutputStream;
+ import java.io.FileReader;
+ import java.io.IOException;
+ import java.io.PrintWriter;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Vector;
+ import javax.swing.JCheckBoxMenuItem;
+ import javax.swing.JColorChooser;
+ import javax.swing.JInternalFrame;
+ import javax.swing.JMenu;
+ import javax.swing.JMenuItem;
+ import javax.swing.JOptionPane;
+ import javax.swing.event.InternalFrameAdapter;
+ import javax.swing.event.InternalFrameEvent;
+ import javax.swing.event.MenuEvent;
+ import javax.swing.event.MenuListener;
+ /**
+  * GUI elements for handlnig an external chimera display
+  * 
+  * @author jprocter
+  *
+  */
+ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
+         ViewSetProvider, JalviewStructureDisplayI
+ {
+   private JalviewChimeraBindingModel jmb;
+   /*
+    * list of sequenceSet ids associated with the view
+    */
+   private ArrayList<String> _aps = new ArrayList<String>();
+   /*
+    * list of alignment panels to use for superposition
+    */
+   private Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
+   /*
+    * list of alignment panels that are used for colouring structures by aligned
+    * sequences
+    */
+   private Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
+   private boolean allChainsSelected = false;
+   private boolean alignAddedStructures = false;
+   AlignmentPanel ap;
+   /*
+    * state flag for PDB retrieval thread
+    */
+   private boolean _started = false;
+   private boolean addingStructures = false;
+   private IProgressIndicator progressBar = null;
+   private String viewId = null;
+   /*
+    * pdb retrieval thread.
+    */
+   private Thread worker = null;
+   /**
+    * Initialise menu options.
+    */
+   private void initMenus()
+   {
+     viewerActionMenu.setText(MessageManager.getString("label.chimera"));
+     viewerColour.setText(MessageManager
+             .getString("label.colour_with_chimera"));
+     viewerColour.setToolTipText(MessageManager
+             .getString("label.let_chimera_manage_structure_colours"));
+     helpItem.setText(MessageManager.getString("label.chimera_help"));
+     seqColour.setSelected(jmb.isColourBySequence());
+     viewerColour.setSelected(!jmb.isColourBySequence());
+     if (_colourwith == null)
+     {
+       _colourwith = new Vector<AlignmentPanel>();
+     }
+     if (_alignwith == null)
+     {
+       _alignwith = new Vector<AlignmentPanel>();
+     }
+     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
+             MessageManager.getString("label.colour_by"), this, _colourwith,
+             new ItemListener()
+             {
+               @Override
+               public void itemStateChanged(ItemEvent e)
+               {
+                 if (!seqColour.isSelected())
+                 {
+                   seqColour.doClick();
+                 }
+                 else
+                 {
+                   // update the Chimera display now.
+                   seqColour_actionPerformed(null);
+                 }
+               }
+             });
+     viewMenu.add(seqColourBy);
+     final ItemListener handler;
+     JMenu alpanels = new ViewSelectionMenu(
+             MessageManager.getString("label.superpose_with"), this,
+             _alignwith, handler = new ItemListener()
+             {
+               @Override
+               public void itemStateChanged(ItemEvent e)
+               {
+                 alignStructs.setEnabled(_alignwith.size() > 0);
+                 alignStructs.setToolTipText(MessageManager
+                         .formatMessage(
+                                 "label.align_structures_using_linked_alignment_views",
+                                 new Object[]
+                                 { new Integer(_alignwith.size()).toString() }));
+               }
+             });
+     handler.itemStateChanged(null);
+     viewerActionMenu.add(alpanels);
+     viewerActionMenu.addMenuListener(new MenuListener()
+     {
+       @Override
+       public void menuSelected(MenuEvent e)
+       {
+         handler.itemStateChanged(null);
+       }
+       @Override
+       public void menuDeselected(MenuEvent e)
+       {
+         // TODO Auto-generated method stub
+       }
+       @Override
+       public void menuCanceled(MenuEvent e)
+       {
+         // TODO Auto-generated method stub
+       }
+     });
+   }
+   /**
+    * add a single PDB structure to a new or existing Chimera view
+    * 
+    * @param pdbentry
+    * @param seq
+    * @param chains
+    * @param ap
+    */
+   public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
+           String[] chains, final AlignmentPanel ap)
+   {
+     super();
+     progressBar = ap.alignFrame;
+     // ////////////////////////////////
+     // Is the pdb file already loaded?
+     String alreadyMapped = ap.getStructureSelectionManager()
+             .alreadyMappedToFile(pdbentry.getId());
+     if (alreadyMapped != null)
+     {
+       int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
+               MessageManager.formatMessage(
+                       "label.pdb_entry_is_already_displayed", new Object[]
+                       { pdbentry.getId() }), MessageManager.formatMessage(
+                       "label.map_sequences_to_visible_window", new Object[]
+                       { pdbentry.getId() }),
+               JOptionPane.YES_NO_CANCEL_OPTION);
+       if (option == JOptionPane.CANCEL_OPTION)
+       {
+         return;
+       }
+       if (option == JOptionPane.YES_OPTION)
+       {
+         // TODO : Fix multiple seq to one chain issue here.
+         ap.getStructureSelectionManager().setMapping(seq, chains,
+                 alreadyMapped, AppletFormatAdapter.FILE);
+         if (ap.seqPanel.seqCanvas.fr != null)
+         {
+           ap.seqPanel.seqCanvas.fr.featuresAdded();
+           ap.paintAlignment(true);
+         }
+         // Now this ChimeraViewFrame is mapped to new sequences. We must add
+         // them to the existing array
+         JInternalFrame[] frames = Desktop.instance.getAllFrames();
+         for (JInternalFrame frame : frames)
+         {
+           if (frame instanceof ChimeraViewFrame)
+           {
+             final ChimeraViewFrame topView = ((ChimeraViewFrame) frame);
+             // JBPNOTE: this looks like a binding routine, rather than a gui
+             // routine
+             for (int pe = 0; pe < topView.jmb.pdbentry.length; pe++)
+             {
+               if (topView.jmb.pdbentry[pe].getFile().equals(alreadyMapped))
+               {
+                 topView.jmb.addSequence(pe, seq);
+                 topView.addAlignmentPanel(ap);
+                 // add it to the set used for colouring
+                 topView.useAlignmentPanelForColourbyseq(ap);
+                 topView.buildChimeraActionMenu();
+                 ap.getStructureSelectionManager()
+                         .sequenceColoursChanged(ap);
+                 break;
+               }
+             }
+           }
+         }
+         return;
+       }
+     }
+     // /////////////////////////////////
+     // Check if there are other Chimera views involving this alignment
+     // and prompt user about adding this molecule to one of them
+     List<ChimeraViewFrame> existingViews = getChimeraWindowsFor(ap);
+     for (ChimeraViewFrame topView : existingViews)
+     {
+       // TODO: highlight topView in view somehow
+       int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
+               MessageManager.formatMessage("label.add_pdbentry_to_view",
+                       new Object[]
+                       { pdbentry.getId(), topView.getTitle() }),
+               MessageManager
+                       .getString("label.align_to_existing_structure_view"),
+               JOptionPane.YES_NO_CANCEL_OPTION);
+       if (option == JOptionPane.CANCEL_OPTION)
+       {
+         return;
+       }
+       if (option == JOptionPane.YES_OPTION)
+       {
+         topView.useAlignmentPanelForSuperposition(ap);
+         topView.addStructure(pdbentry, seq, chains, true, ap.alignFrame);
+         return;
+       }
+     }
+     // /////////////////////////////////
+     openNewChimera(ap, new PDBEntry[]
+     { pdbentry }, new SequenceI[][]
+     { seq });
+   }
+   private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
+           SequenceI[][] seqs)
+   {
+     progressBar = ap.alignFrame;
+     jmb = new JalviewChimeraBindingModel(this,
+             ap.getStructureSelectionManager(), pdbentrys, seqs, null, null);
+     addAlignmentPanel(ap);
+     useAlignmentPanelForColourbyseq(ap);
+     if (pdbentrys.length > 1)
+     {
+       alignAddedStructures = true;
+       useAlignmentPanelForSuperposition(ap);
+     }
+     jmb.setColourBySequence(true);
+     setSize(400, 400); // probably should be a configurable/dynamic default here
+     initMenus();
+     worker = null;
+     {
+       addingStructures = false;
+       worker = new Thread(this);
+       worker.start();
+     }
+     this.addInternalFrameListener(new InternalFrameAdapter()
+     {
+       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
+       {
+         closeViewer();
+       }
+     });
+   }
+   /**
+    * create a new viewer containing several structures superimposed using the
+    * given alignPanel.
+    * 
+    * @param ap
+    * @param pe
+    * @param seqs
+    */
+   public ChimeraViewFrame(AlignmentPanel ap, PDBEntry[] pe,
+           SequenceI[][] seqs)
+   {
+     super();
+     openNewChimera(ap, pe, seqs);
+   }
+   public AlignmentPanel[] getAllAlignmentPanels()
+   {
+     AlignmentPanel[] t, list = new AlignmentPanel[0];
+     for (String setid : _aps)
+     {
+       AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
+       if (panels != null)
+       {
+         t = new AlignmentPanel[list.length + panels.length];
+         System.arraycopy(list, 0, t, 0, list.length);
+         System.arraycopy(panels, 0, t, list.length, panels.length);
+         list = t;
+       }
+     }
+     return list;
+   }
+   /**
+    * set the primary alignmentPanel reference and add another alignPanel to the
+    * list of ones to use for colouring and aligning
+    * 
+    * @param nap
+    */
+   public void addAlignmentPanel(AlignmentPanel nap)
+   {
+     if (ap == null)
+     {
+       ap = nap;
+     }
+     if (!_aps.contains(nap.av.getSequenceSetId()))
+     {
+       _aps.add(nap.av.getSequenceSetId());
+     }
+   }
+   /**
+    * remove any references held to the given alignment panel
+    * 
+    * @param nap
+    */
+   public void removeAlignmentPanel(AlignmentPanel nap)
+   {
+     try
+     {
+       _alignwith.remove(nap);
+       _colourwith.remove(nap);
+       if (ap == nap)
+       {
+         ap = null;
+         for (AlignmentPanel aps : getAllAlignmentPanels())
+         {
+           if (aps != nap)
+           {
+             ap = aps;
+             break;
+           }
+         }
+       }
+     } catch (Exception ex)
+     {
+     }
+     if (ap != null)
+     {
+       buildChimeraActionMenu();
+     }
+   }
+   public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
+   {
+     addAlignmentPanel(nap);
+     if (!_alignwith.contains(nap))
+     {
+       _alignwith.add(nap);
+     }
+   }
+   public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
+   {
+     if (_alignwith.contains(nap))
+     {
+       _alignwith.remove(nap);
+     }
+   }
+   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
+           boolean enableColourBySeq)
+   {
+     useAlignmentPanelForColourbyseq(nap);
+     jmb.setColourBySequence(enableColourBySeq);
+     seqColour.setSelected(enableColourBySeq);
+     viewerColour.setSelected(!enableColourBySeq);
+   }
+   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
+   {
+     addAlignmentPanel(nap);
+     if (!_colourwith.contains(nap))
+     {
+       _colourwith.add(nap);
+     }
+   }
+   public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
+   {
+     if (_colourwith.contains(nap))
+     {
+       _colourwith.remove(nap);
+     }
+   }
+   /**
+    * add a new structure (with associated sequences and chains) to this viewer,
+    * retrieving it if necessary first.
+    * 
+    * @param pdbentry
+    * @param seq
+    * @param chains
+    * @param alignFrame
+    * @param align
+    *          if true, new structure(s) will be align using associated alignment
+    */
+   private void addStructure(final PDBEntry pdbentry, final SequenceI[] seq,
+           final String[] chains, final boolean b,
+           final IProgressIndicator alignFrame)
+   {
+     if (pdbentry.getFile() == null)
+     {
+       if (worker != null && worker.isAlive())
+       {
+         // a retrieval is in progress, wait around and add ourselves to the
+         // queue.
+         new Thread(new Runnable()
+         {
+           public void run()
+           {
+             while (worker != null && worker.isAlive() && _started)
+             {
+               try
+               {
+                 Thread.sleep(100 + ((int) Math.random() * 100));
+               } catch (Exception e)
+               {
+               }
+             }
+             // and call ourselves again.
+             addStructure(pdbentry, seq, chains, b, alignFrame);
+           }
+         }).start();
+         return;
+       }
+     }
+     // otherwise, start adding the structure.
+     jmb.addSequenceAndChain(new PDBEntry[]
+     { pdbentry }, new SequenceI[][]
+     { seq }, new String[][]
+     { chains });
+     addingStructures = true;
+     _started = false;
+     alignAddedStructures = b;
+     progressBar = alignFrame; // visual indication happens on caller frame.
+     (worker = new Thread(this)).start();
+     return;
+   }
+   private List<ChimeraViewFrame> getChimeraWindowsFor(AlignmentPanel apanel)
+   {
+     List<ChimeraViewFrame> result = new ArrayList<ChimeraViewFrame>();
+     JInternalFrame[] frames = Desktop.instance.getAllFrames();
+     for (JInternalFrame frame : frames)
+     {
+       if (frame instanceof ChimeraViewFrame)
+       {
+         if (((ChimeraViewFrame) frame).isLinkedWith(apanel))
+         {
+           result.add((ChimeraViewFrame) frame);
+         }
+       }
+     }
+     return result;
+   }
+   void initChimera(String command)
+   {
+     jmb.setFinishedInit(false);
+     // TODO: consider waiting until the structure/view is fully loaded before
+     // displaying
+     jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(true),
+             getBounds().width, getBounds().height);
+     if (command == null)
+     {
+       command = "";
+     }
+     jmb.evalStateCommand(command, false);
+     jmb.setFinishedInit(true);
+   }
+   void setChainMenuItems(List<String> chainNames)
+   {
+     chainMenu.removeAll();
+     if (chainNames == null)
+     {
+       return;
+     }
+     JMenuItem menuItem = new JMenuItem(
+             MessageManager.getString("label.all"));
+     menuItem.addActionListener(new ActionListener()
+     {
+       public void actionPerformed(ActionEvent evt)
+       {
+         allChainsSelected = true;
+         for (int i = 0; i < chainMenu.getItemCount(); i++)
+         {
+           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
+           {
+             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
+           }
+         }
+         centerViewer();
+         allChainsSelected = false;
+       }
+     });
+     chainMenu.add(menuItem);
+     for (String chainName : chainNames)
+     {
+       menuItem = new JCheckBoxMenuItem(chainName, true);
+       menuItem.addItemListener(new ItemListener()
+       {
+         public void itemStateChanged(ItemEvent evt)
+         {
+           if (!allChainsSelected)
+           {
+             centerViewer();
+           }
+         }
+       });
+       chainMenu.add(menuItem);
+     }
+   }
+   void centerViewer()
+   {
+     List<String> toshow = new ArrayList<String>();
+     for (int i = 0; i < chainMenu.getItemCount(); i++)
+     {
+       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
+       {
+         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
+         if (item.isSelected())
+         {
+           toshow.add(item.getText());
+         }
+       }
+     }
+     jmb.centerViewer(toshow);
+   }
+   /**
+    * Close down this instance of Jalview's Chimera viewer, giving the user the
+    * option to close the associated Chimera window (process). They may wish to
+    * keep it open until they have had an opportunity to save any work.
+    */
+   public void closeViewer()
+   {
+     if (jmb.isChimeraRunning())
+     {
+       String prompt = MessageManager
+               .formatMessage("label.confirm_close_chimera", new Object[]
+               { jmb.getViewerTitle(false) });
+       prompt = JvSwingUtils.wrapTooltip(true, prompt);
+       int confirm = JOptionPane.showConfirmDialog(this, prompt,
+               MessageManager.getString("label.close_viewer"),
+               JOptionPane.YES_NO_OPTION);
+       jmb.closeViewer(confirm == JOptionPane.YES_OPTION);
+     }
+     ap = null;
+     _aps.clear();
+     _alignwith.clear();
+     _colourwith.clear();
+     // TODO: check for memory leaks where instance isn't finalised because jmb
+     // holds a reference to the window
+     jmb = null;
+   }
+   /**
+    * Open any newly added PDB structures in Chimera, having first fetched data
+    * from PDB (if not already saved).
+    */
+   public void run()
+   {
+     _started = true;
+     // todo - record which pdbids were successfully imported.
+     StringBuilder errormsgs = new StringBuilder(128);
+     StringBuilder files = new StringBuilder(128);
+     List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
+     List<Integer> filePDBpos = new ArrayList<Integer>();
+     PDBEntry thePdbEntry = null;
+     try
+     {
+       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
+       // TODO: replace with reference fetching/transfer code (validate PDBentry
+       // as a DBRef?)
+       for (int pi = 0; pi < jmb.pdbentry.length; pi++)
+       {
+         String file = null;
+         thePdbEntry = jmb.pdbentry[pi];
+         if (thePdbEntry.getFile() == null)
+         {
+           /*
+            * Retrieve PDB data, save to file, attach to PDBEntry
+            */
+           file = fetchPdbFile(thePdbEntry);
+           if (file == null)
+           {
+             errormsgs.append("'" + thePdbEntry.getId() + "' ");
+           }
+         }
+         else
+         {
+           /*
+            * Got file already - ignore if already loaded in Chimera.
+            */
+           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
+                   .getPath();
+           if (curfiles != null && curfiles.length > 0)
+           {
+             addingStructures = true; // already files loaded.
+             for (int c = 0; c < curfiles.length; c++)
+             {
+               if (curfiles[c].equals(file))
+               {
+                 file = null;
+                 break;
+               }
+             }
+           }
+         }
+         if (file != null)
+         {
+           filePDB.add(thePdbEntry);
+           filePDBpos.add(Integer.valueOf(pi));
+           files.append(" \"" + Platform.escapeString(file) + "\"");
+         }
+       }
+     } catch (OutOfMemoryError oomerror)
+     {
+       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
+               oomerror);
+     } catch (Exception ex)
+     {
+       ex.printStackTrace();
+       errormsgs.append("When retrieving pdbfiles for '"
+               + thePdbEntry.getId() + "'");
+     }
+     if (errormsgs.length() > 0)
+     {
+       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
+               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
+                       new Object[]
+                       { errormsgs.toString() }), MessageManager
+               .getString("label.couldnt_load_file"),
+               JOptionPane.ERROR_MESSAGE);
+     }
+     if (files.length() > 0)
+     {
+       if (!addingStructures)
+       {
+         try
+         {
+           initChimera("");
+         } catch (Exception ex)
+         {
+           Cache.log.error("Couldn't open Chimera viewer!", ex);
+         }
+       }
+       int num = -1;
+       for (PDBEntry pe : filePDB)
+       {
+         num++;
+         if (pe.getFile() != null)
+         {
+           try
+           {
+             int pos = filePDBpos.get(num).intValue();
+             jmb.openFile(pe);
+             jmb.addSequence(pos, jmb.sequence[pos]);
+             File fl = new File(pe.getFile());
+             String protocol = AppletFormatAdapter.URL;
+             try
+             {
+               if (fl.exists())
+               {
+                 protocol = AppletFormatAdapter.FILE;
+               }
+             } catch (Throwable e)
+             {
+             }
+             // Explicitly map to the filename used by Chimera ;
+             // TODO: use pe.getId() instead of pe.getFile() ?
+             jmb.ssm.setMapping(jmb.sequence[pos], null, pe.getFile(),
+                     protocol);
+           } catch (OutOfMemoryError oomerror)
+           {
+             new OOMWarning(
+                     "When trying to open and map structures from Chimera!",
+                     oomerror);
+           } catch (Exception ex)
+           {
+             Cache.log.error("Couldn't open " + pe.getFile()
+                     + " in Chimera viewer!", ex);
+           } finally
+           {
+             Cache.log.debug("File locations are " + files);
+           }
+         }
+       }
+       jmb.setFinishedInit(true);
+       jmb.setLoadingFromArchive(false);
+       // refresh the sequence colours for the new structure(s)
+       for (AlignmentPanel ap : _colourwith)
+       {
+         jmb.updateColours(ap);
+       }
+       // do superposition if asked to
+       if (alignAddedStructures)
+       {
+         new Thread(new Runnable()
+         {
+           public void run()
+           {
+             alignStructs_withAllAlignPanels();
+           }
+         }).start();
+         alignAddedStructures = false;
+       }
+       addingStructures = false;
+     }
+     _started = false;
+     worker = null;
+   }
+   /**
+    * Fetch PDB data and save to a local file. Returns the full path to the file,
+    * or null if fetch fails.
+    * 
+    * @param processingEntry
+    * @return
+    * @throws Exception
+    */
+   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
+   {
+     String filePath = null;
+     Pdb pdbclient = new Pdb();
+     AlignmentI pdbseq = null;
+     String pdbid = processingEntry.getId();
+     long hdl = pdbid.hashCode() - System.currentTimeMillis();
+     if (progressBar != null)
+     {
+       progressBar.setProgressBar(MessageManager.formatMessage(
+               "status.fetching_pdb", new Object[]
+               { pdbid }), hdl);
+     }
+     try
+     {
+       pdbseq = pdbclient.getSequenceRecords(pdbid);
+     } catch (OutOfMemoryError oomerror)
+     {
+       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
+     } finally
+     {
+       if (progressBar != null)
+       {
+         progressBar.setProgressBar(
+                 MessageManager.getString("label.state_completed"), hdl);
+       }
+     }
+     /*
+      * If PDB data were saved and are not invalid (empty alignment), return the
+      * file path.
+      */
+     if (pdbseq != null && pdbseq.getHeight() > 0)
+     {
+       // just use the file name from the first sequence's first PDBEntry
+       filePath = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
+               .elementAt(0)).getFile()).getAbsolutePath();
+       processingEntry.setFile(filePath);
+     }
+     return filePath;
+   }
+   @Override
+   public void pdbFile_actionPerformed(ActionEvent actionEvent)
+   {
+     JalviewFileChooser chooser = new JalviewFileChooser(
+             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
+     chooser.setFileView(new JalviewFileView());
+     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
+     chooser.setToolTipText(MessageManager.getString("action.save"));
+     int value = chooser.showSaveDialog(this);
+     if (value == JalviewFileChooser.APPROVE_OPTION)
+     {
+       BufferedReader in = null;
+       try
+       {
+         // TODO: cope with multiple PDB files in view
+         in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
+         File outFile = chooser.getSelectedFile();
+         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
+         String data;
+         while ((data = in.readLine()) != null)
+         {
+           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
+           {
+             out.println(data);
+           }
+         }
+         out.close();
+       } catch (Exception ex)
+       {
+         ex.printStackTrace();
+       } finally
+       {
+         if (in != null)
+         {
+           try
+           {
+             in.close();
+           } catch (IOException e)
+           {
+             e.printStackTrace();
+           }
+         }
+       }
+     }
+   }
+   @Override
+   public void viewMapping_actionPerformed(ActionEvent actionEvent)
+   {
+     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
+     try
+     {
+       for (int pdbe = 0; pdbe < jmb.pdbentry.length; pdbe++)
+       {
+         cap.appendText(jmb.printMapping(jmb.pdbentry[pdbe].getFile()));
+         cap.appendText("\n");
+       }
+     } catch (OutOfMemoryError e)
+     {
+       new OOMWarning(
+               "composing sequence-structure alignments for display in text box.",
+               e);
+       cap.dispose();
+       return;
+     }
+     jalview.gui.Desktop.addInternalFrame(cap,
+             MessageManager.getString("label.pdb_sequence_mapping"), 550,
+             600);
+   }
+   @Override
+   public void eps_actionPerformed(ActionEvent e)
+   {
+     throw new Error(
+             MessageManager
+                     .getString("error.eps_generation_not_implemented"));
+   }
+   @Override
+   public void png_actionPerformed(ActionEvent e)
+   {
+     throw new Error(
+             MessageManager
+                     .getString("error.png_generation_not_implemented"));
+   }
+   @Override
+   public void viewerColour_actionPerformed(ActionEvent actionEvent)
+   {
+     if (viewerColour.isSelected())
+     {
+       // disable automatic sequence colouring.
+       jmb.setColourBySequence(false);
+     }
+   }
+   @Override
+   public void seqColour_actionPerformed(ActionEvent actionEvent)
+   {
+     jmb.setColourBySequence(seqColour.isSelected());
+     if (_colourwith == null)
+     {
+       _colourwith = new Vector<AlignmentPanel>();
+     }
+     if (jmb.isColourBySequence())
+     {
+       if (!jmb.isLoadingFromArchive())
+       {
+         if (_colourwith.size() == 0 && ap != null)
+         {
+           // Make the currently displayed alignment panel the associated view
+           _colourwith.add(ap.alignFrame.alignPanel);
+         }
+       }
+       // Set the colour using the current view for the associated alignframe
+       for (AlignmentPanel ap : _colourwith)
+       {
 -        jmb.colourBySequence(ap.av.showSequenceFeatures, ap);
++        jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
+       }
+     }
+   }
+   @Override
+   public void chainColour_actionPerformed(ActionEvent actionEvent)
+   {
+     chainColour.setSelected(true);
+     jmb.colourByChain();
+   }
+   @Override
+   public void chargeColour_actionPerformed(ActionEvent actionEvent)
+   {
+     chargeColour.setSelected(true);
+     jmb.colourByCharge();
+   }
+   @Override
+   public void zappoColour_actionPerformed(ActionEvent actionEvent)
+   {
+     zappoColour.setSelected(true);
+     jmb.setJalviewColourScheme(new ZappoColourScheme());
+   }
+   @Override
+   public void taylorColour_actionPerformed(ActionEvent actionEvent)
+   {
+     taylorColour.setSelected(true);
+     jmb.setJalviewColourScheme(new TaylorColourScheme());
+   }
+   @Override
+   public void hydroColour_actionPerformed(ActionEvent actionEvent)
+   {
+     hydroColour.setSelected(true);
+     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
+   }
+   @Override
+   public void helixColour_actionPerformed(ActionEvent actionEvent)
+   {
+     helixColour.setSelected(true);
+     jmb.setJalviewColourScheme(new HelixColourScheme());
+   }
+   @Override
+   public void strandColour_actionPerformed(ActionEvent actionEvent)
+   {
+     strandColour.setSelected(true);
+     jmb.setJalviewColourScheme(new StrandColourScheme());
+   }
+   @Override
+   public void turnColour_actionPerformed(ActionEvent actionEvent)
+   {
+     turnColour.setSelected(true);
+     jmb.setJalviewColourScheme(new TurnColourScheme());
+   }
+   @Override
+   public void buriedColour_actionPerformed(ActionEvent actionEvent)
+   {
+     buriedColour.setSelected(true);
+     jmb.setJalviewColourScheme(new BuriedColourScheme());
+   }
+   @Override
+   public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent)
+   {
+     setJalviewColourScheme(new PurinePyrimidineColourScheme());
+   }
+   @Override
+   public void userColour_actionPerformed(ActionEvent actionEvent)
+   {
+     userColour.setSelected(true);
+     new UserDefinedColours(this, null);
+   }
+   @Override
+   public void backGround_actionPerformed(ActionEvent actionEvent)
+   {
+     java.awt.Color col = JColorChooser
+             .showDialog(this, MessageManager
+                     .getString("label.select_backgroud_colour"), null);
+     if (col != null)
+     {
+       jmb.setBackgroundColour(col);
+     }
+   }
+   @Override
+   public void showHelp_actionPerformed(ActionEvent actionEvent)
+   {
+     try
+     {
+       jalview.util.BrowserLauncher
+               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
+     } catch (Exception ex)
+     {
+     }
+   }
+   public String getViewId()
+   {
+     if (viewId == null)
+     {
+       viewId = System.currentTimeMillis() + "." + this.hashCode();
+     }
+     return viewId;
+   }
+   public void updateTitleAndMenus()
+   {
+     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
+     {
+       repaint();
+       return;
+     }
+     setChainMenuItems(jmb.chainNames);
+     this.setTitle(jmb.getViewerTitle(true));
+     if (jmb.getPdbFile().length > 1 && jmb.sequence.length > 1)
+     {
+       viewerActionMenu.setVisible(true);
+     }
+     if (!jmb.isLoadingFromArchive())
+     {
+       seqColour_actionPerformed(null);
+     }
+   }
+   protected void buildChimeraActionMenu()
+   {
+     if (_alignwith == null)
+     {
+       _alignwith = new Vector<AlignmentPanel>();
+     }
+     if (_alignwith.size() == 0 && ap != null)
+     {
+       _alignwith.add(ap);
+     }
+     ;
+     for (Component c : viewerActionMenu.getMenuComponents())
+     {
+       if (c != alignStructs)
+       {
+         viewerActionMenu.remove((JMenuItem) c);
+       }
+     }
+   }
+   /*
+    * (non-Javadoc)
+    * 
+    * @see
+    * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event
+    * .ActionEvent)
+    */
+   @Override
+   protected void alignStructs_actionPerformed(ActionEvent actionEvent)
+   {
+     alignStructs_withAllAlignPanels();
+   }
+   private void alignStructs_withAllAlignPanels()
+   {
+     if (ap == null)
+     {
+       return;
+     }
+     ;
+     if (_alignwith.size() == 0)
+     {
+       _alignwith.add(ap);
+     }
+     ;
+     try
+     {
+       AlignmentI[] als = new Alignment[_alignwith.size()];
+       ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
+       int[] alm = new int[_alignwith.size()];
+       int a = 0;
+       for (AlignmentPanel ap : _alignwith)
+       {
+         als[a] = ap.av.getAlignment();
+         alm[a] = -1;
+         alc[a++] = ap.av.getColumnSelection();
+       }
+       jmb.superposeStructures(als, alm, alc);
+     } catch (Exception e)
+     {
+       StringBuffer sp = new StringBuffer();
+       for (AlignmentPanel ap : _alignwith)
+       {
+         sp.append("'" + ap.alignFrame.getTitle() + "' ");
+       }
+       Cache.log.info("Couldn't align structures with the " + sp.toString()
+               + "associated alignment panels.", e);
+     }
+   }
+   public void setJalviewColourScheme(ColourSchemeI ucs)
+   {
+     jmb.setJalviewColourScheme(ucs);
+   }
+   /**
+    * 
+    * @param alignment
+    * @return first alignment panel displaying given alignment, or the default
+    *         alignment panel
+    */
+   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
+   {
+     for (AlignmentPanel ap : getAllAlignmentPanels())
+     {
+       if (ap.av.getAlignment() == alignment)
+       {
+         return ap;
+       }
+     }
+     return ap;
+   }
+   /**
+    * 
+    * @param ap2
+    * @return true if this Chimera instance is linked with the given alignPanel
+    */
+   public boolean isLinkedWith(AlignmentPanel ap2)
+   {
+     return _aps.contains(ap2.av.getSequenceSetId());
+   }
+   public boolean isUsedforaligment(AlignmentPanel ap2)
+   {
+     return (_alignwith != null) && _alignwith.contains(ap2);
+   }
+   public boolean isUsedforcolourby(AlignmentPanel ap2)
+   {
+     return (_colourwith != null) && _colourwith.contains(ap2);
+   }
+   /**
+    * 
+    * @return TRUE if the view is NOT being coloured by sequence associations.
+    */
+   public boolean isColouredByChimera()
+   {
+     return !jmb.isColourBySequence();
+   }
+   public SequenceStructureBinding getBinding()
+   {
+     return jmb;
+   }
+ }
Simple merge
   */
  package jalview.gui;
  
- import java.io.*;
- import java.util.*;
- import java.util.List;
- 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.*;
--import jalview.analysis.AlignmentSorter;
- import jalview.api.FeaturesDisplayedI;
  import jalview.bin.Cache;
--import jalview.commands.OrderCommand;
- import jalview.datamodel.*;
- import jalview.io.*;
 -import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SequenceFeature;
 -import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import jalview.io.JalviewFileChooser;
  import jalview.schemes.AnnotationColourGradient;
  import jalview.schemes.GraduatedColor;
  import jalview.util.MessageManager;
  import jalview.ws.dbsources.das.api.jalviewSourceI;
  
+ import java.awt.BorderLayout;
+ import java.awt.Color;
+ import java.awt.Component;
+ import java.awt.Font;
+ import java.awt.Graphics;
+ import java.awt.GridLayout;
+ import java.awt.Rectangle;
+ import java.awt.event.ActionEvent;
+ import java.awt.event.ActionListener;
+ import java.awt.event.ItemEvent;
+ import java.awt.event.ItemListener;
+ import java.awt.event.MouseAdapter;
+ import java.awt.event.MouseEvent;
+ import java.awt.event.MouseMotionAdapter;
+ import java.beans.PropertyChangeEvent;
+ import java.beans.PropertyChangeListener;
+ import java.io.File;
+ import java.io.FileInputStream;
+ import java.io.FileOutputStream;
+ import java.io.InputStreamReader;
+ import java.io.OutputStreamWriter;
+ import java.io.PrintWriter;
 -import java.util.ArrayList;
+ import java.util.Hashtable;
+ import java.util.Iterator;
+ import java.util.List;
++import java.util.Set;
+ import java.util.Vector;
+ import javax.swing.AbstractCellEditor;
+ import javax.swing.BorderFactory;
+ import javax.swing.Icon;
+ import javax.swing.JButton;
+ import javax.swing.JCheckBox;
+ import javax.swing.JCheckBoxMenuItem;
+ import javax.swing.JColorChooser;
+ import javax.swing.JDialog;
+ import javax.swing.JInternalFrame;
+ import javax.swing.JLabel;
+ import javax.swing.JLayeredPane;
+ import javax.swing.JMenuItem;
+ import javax.swing.JOptionPane;
+ import javax.swing.JPanel;
+ import javax.swing.JPopupMenu;
+ import javax.swing.JScrollPane;
+ import javax.swing.JSlider;
+ import javax.swing.JTabbedPane;
+ import javax.swing.JTable;
+ import javax.swing.ListSelectionModel;
+ import javax.swing.SwingConstants;
+ import javax.swing.SwingUtilities;
+ import javax.swing.event.ChangeEvent;
+ import javax.swing.event.ChangeListener;
+ import javax.swing.table.AbstractTableModel;
+ import javax.swing.table.TableCellEditor;
+ import javax.swing.table.TableCellRenderer;
  public class FeatureSettings extends JPanel
  {
    DasSourceBrowser dassourceBrowser;
        public void mousePressed(MouseEvent evt)
        {
          selectedRow = table.rowAtPoint(evt.getPoint());
-         if (evt.isPopupTrigger())
+         if (SwingUtilities.isRightMouseButton(evt))
          {
            popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
 -                  table.getValueAt(selectedRow, 1), fr.minmax, evt.getX(),
 -                  evt.getY());
 +                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
 +                  evt.getX(), evt.getY());
          }
          else if (evt.getClickCount() == 2)
          {
                    (String) table.getValueAt(selectedRow, 0));
          }
        }
+       // isPopupTrigger fires on mouseReleased on Mac
+       @Override
+       public void mouseReleased(MouseEvent evt)
+       {
+         selectedRow = table.rowAtPoint(evt.getPoint());
+         if (evt.isPopupTrigger())
+         {
+           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
 -                  table.getValueAt(selectedRow, 1), fr.minmax, evt.getX(),
++                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
++                  evt.getX(),
+                   evt.getY());
+         }
+       }
      });
  
      table.addMouseMotionListener(new MouseMotionAdapter()
      {
        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()
    public void orderByAvWidth()
    {
      if (table == null || table.getModel() == null)
++    {
        return;
++    }
      Object[][] data = ((FeatureTableModel) table.getModel()).getData();
      float[] width = new float[data.length];
      float[] awidth;
          width[i] = 0;
        }
        if (max < width[i])
++      {
          max = width[i];
++      }
      }
      boolean sort = false;
      for (int i = 0; i < width.length; i++)
          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();
Simple merge
@@@ -1359,7 -1419,16 +1423,17 @@@ public class Jalview2XM
          calcIdSet.add(aa[i].getCalcId());
          an.setCalcId(aa[i].getCalcId());
        }
+       if (aa[i].hasProperties())
+       {
+         for (String pr : aa[i].getProperties())
+         {
+           Property prop = new Property();
+           prop.setName(pr);
+           prop.setValue(aa[i].getProperty(pr));
+           an.addProperty(prop);
+         }
+       }
 +
        AnnotationElement ae;
        if (aa[i].annotations != null)
        {
    {
      skipList = skipList2;
    }
--
  }
   */
  package jalview.gui;
  
- import java.io.*;
- import java.util.*;
- import java.util.jar.*;
+ import jalview.binding.Annotation;
+ import jalview.binding.AnnotationElement;
+ import jalview.binding.Features;
+ import jalview.binding.JGroup;
+ import jalview.binding.JSeq;
+ import jalview.binding.JalviewModel;
+ import jalview.binding.JalviewModelSequence;
+ import jalview.binding.Pdbids;
+ import jalview.binding.Sequence;
+ import jalview.binding.SequenceSet;
+ import jalview.binding.Setting;
+ import jalview.binding.Tree;
+ import jalview.binding.UserColours;
+ import jalview.binding.Viewport;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.ColourSchemeProperty;
+ import jalview.schemes.ResidueProperties;
+ import jalview.structure.StructureSelectionManager;
+ import jalview.util.MessageManager;
+ import jalview.util.jarInputStreamProvider;
  
- import javax.swing.*;
+ import java.io.InputStreamReader;
+ import java.util.Hashtable;
+ import java.util.Vector;
+ import java.util.jar.JarEntry;
+ import java.util.jar.JarInputStream;
  
- import org.exolab.castor.xml.*;
+ import javax.swing.JOptionPane;
  
- import jalview.binding.*;
- import jalview.schemes.*;
 +import jalview.util.MessageManager;
 +import jalview.util.jarInputStreamProvider;
+ import org.exolab.castor.xml.IDResolver;
 +import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
  
  /**
   * DOCUMENT ME!
index 0000000,c582c5d..d22c46c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,104 +1,104 @@@
+ package jalview.gui;
+ import jalview.api.AlignmentViewPanel;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
+ import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
+ import jalview.structure.StructureSelectionManager;
+ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
+ {
+   private ChimeraViewFrame cvf;
+   public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame,
+           StructureSelectionManager ssm, PDBEntry[] pdbentry,
+           SequenceI[][] sequenceIs, String[][] chains, String protocol)
+   {
+     super(ssm, pdbentry, sequenceIs, chains, protocol);
+     cvf = chimeraViewFrame;
+   }
+   FeatureRenderer fr = null;
+   @Override
+   public jalview.api.FeatureRenderer getFeatureRenderer(
+           AlignmentViewPanel alignment)
+   {
+     AlignmentPanel ap = (alignment == null) ? cvf.ap
+             : (AlignmentPanel) alignment;
 -    if (ap.av.showSequenceFeatures)
++    if (ap.av.isShowSequenceFeatures())
+     {
+       if (fr == null)
+       {
 -        fr = ap.cloneFeatureRenderer();
++        fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer();
+       }
+       else
+       {
+         ap.updateFeatureRenderer(fr);
+       }
+     }
+     return fr;
+   }
+   @Override
+   public jalview.api.SequenceRenderer getSequenceRenderer(
+           AlignmentViewPanel alignment)
+   {
+     return new SequenceRenderer(((AlignmentPanel) alignment).av);
+   }
+   @Override
+   public void refreshGUI()
+   {
+     // appJmolWindow.repaint();
+     javax.swing.SwingUtilities.invokeLater(new Runnable()
+     {
+       public void run()
+       {
+         cvf.updateTitleAndMenus();
+         cvf.revalidate();
+       }
+     });
+   }
+   public void updateColours(Object source)
+   {
+     AlignmentPanel ap = (AlignmentPanel) source, topap;
+     // ignore events from panels not used to colour this view
+     if (!cvf.isUsedforcolourby(ap))
+     {
+       return;
+     }
+     if (!isLoadingFromArchive())
+     {
 -      colourBySequence(ap.av.getShowSequenceFeatures(), ap);
++      colourBySequence(ap.av.isShowSequenceFeatures(), ap);
+     }
+   }
+   @Override
+   public void releaseReferences(Object svl)
+   {
+     // TODO Auto-generated method stub
+   }
+   @Override
+   protected void releaseUIResources()
+   {
+     // TODO Auto-generated method stub
+   }
+   @Override
+   public void refreshPdbEntries()
+   {
+     // TODO Auto-generated method stub
+   }
+   @Override
+   public void showUrl(String url, String target)
+   {
+     // TODO Auto-generated method stub
+   }
+ }
Simple merge
Simple merge
Simple merge
index c3b6c1e,0000000..30d14c2
mode 100644,000000..100644
--- /dev/null
@@@ -1,942 -1,0 +1,942 @@@
 +package jalview.viewmodel.seqfeatures;
 +
 +import jalview.api.AlignViewportI;
 +import jalview.api.FeaturesDisplayedI;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.SequenceFeature;
 +import jalview.datamodel.SequenceI;
 +import jalview.renderer.seqfeatures.FeatureRenderer;
 +import jalview.schemes.GraduatedColor;
 +import jalview.viewmodel.AlignmentViewport;
 +
 +import java.awt.Color;
 +import java.beans.PropertyChangeListener;
 +import java.beans.PropertyChangeSupport;
 +import java.util.ArrayList;
 +import java.util.Arrays;
- import java.util.Enumeration;
 +import java.util.Hashtable;
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
- import java.util.Vector;
 +import java.util.concurrent.ConcurrentHashMap;
 +
 +public abstract class FeatureRendererModel implements
 +        jalview.api.FeatureRenderer
 +{
 +
 +  /**
 +   * global transparency for feature
 +   */
 +  protected float transparency = 1.0f;
 +
 +  protected Map<String, Object> featureColours = new ConcurrentHashMap<String, Object>();
 +
 +  protected Map<String, Boolean> featureGroups = new ConcurrentHashMap<String, Boolean>();
 +
 +  protected Object currentColour;
 +
 +  protected String[] renderOrder;
 +
 +  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
 +          this);
 +
 +  protected AlignmentViewport av;
 +
 +  public AlignViewportI getViewport()
 +  {
 +    return av;
 +  }
 +
 +  public FeatureRendererSettings getSettings()
 +  {
 +    return new FeatureRendererSettings(this);
 +  }
 +
 +  public void transferSettings(FeatureRendererSettings fr)
 +  {
 +    this.renderOrder = fr.renderOrder;
 +    this.featureGroups = fr.featureGroups;
 +    this.featureColours = fr.featureColours;
 +    this.transparency = fr.transparency;
 +    this.featureOrder = fr.featureOrder;
 +  }
 +
 +  /**
 +   * update from another feature renderer
 +   * 
 +   * @param fr
 +   *          settings to copy
 +   */
 +  public void transferSettings(jalview.api.FeatureRenderer _fr)
 +  {
 +    FeatureRenderer fr = (FeatureRenderer) _fr;
 +    FeatureRendererSettings frs = new FeatureRendererSettings(fr);
 +    this.renderOrder = frs.renderOrder;
 +    this.featureGroups = frs.featureGroups;
 +    this.featureColours = frs.featureColours;
 +    this.transparency = frs.transparency;
 +    this.featureOrder = frs.featureOrder;
 +    if (av != null && av != fr.getViewport())
 +    {
 +      // copy over the displayed feature settings
 +      if (_fr.getFeaturesDisplayed() != null)
 +      {
 +        FeaturesDisplayedI fd = getFeaturesDisplayed();
 +        if (fd == null)
 +        {
 +          setFeaturesDisplayedFrom(_fr.getFeaturesDisplayed());
 +        }
 +        else
 +        {
 +          synchronized (fd)
 +          {
 +            fd.clear();
 +            java.util.Iterator<String> fdisp = _fr.getFeaturesDisplayed()
 +                    .getVisibleFeatures();
 +            while (fdisp.hasNext())
 +            {
 +              fd.setVisible(fdisp.next());
 +            }
 +          }
 +        }
 +      }
 +    }
 +  }
 +
 +  public void setFeaturesDisplayedFrom(FeaturesDisplayedI featuresDisplayed)
 +  {
 +    av.setFeaturesDisplayed(new FeaturesDisplayed(featuresDisplayed));
 +  }
 +
 +  @Override
 +  public void setVisible(String featureType)
 +  {
 +    FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
 +    if (fdi == null)
 +    {
 +      av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
 +    }
 +    if (!fdi.isRegistered(featureType))
 +    {
 +      pushFeatureType(Arrays.asList(new String[]
 +      { featureType }));
 +    }
 +    fdi.setVisible(featureType);
 +  }
 +
 +  @Override
 +  public void setAllVisible(List<String> featureTypes)
 +  {
 +    FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
 +    if (fdi == null)
 +    {
 +      av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
 +    }
 +    List<String> nft = new ArrayList<String>();
 +    for (String featureType : featureTypes)
 +    {
 +      if (!fdi.isRegistered(featureType))
 +      {
 +        nft.add(featureType);
 +      }
 +    }
 +    if (nft.size() > 0)
 +    {
 +      pushFeatureType(nft);
 +    }
 +    fdi.setAllVisible(featureTypes);
 +  }
 +
 +  /**
 +   * push a set of new types onto the render order stack. Note - this is a
 +   * direct mechanism rather than the one employed in updateRenderOrder
 +   * 
 +   * @param types
 +   */
 +  private void pushFeatureType(List<String> types)
 +  {
 +
 +    int ts = types.size();
 +    String neworder[] = new String[(renderOrder == null ? 0
 +            : renderOrder.length) + ts];
 +    types.toArray(neworder);
 +    if (renderOrder != null)
 +    {
 +      System.arraycopy(neworder,0,neworder,renderOrder.length,ts);
 +      System.arraycopy(renderOrder, 0, neworder, 0, renderOrder.length);
 +    }
 +    renderOrder = neworder;
 +  }
 +
 +  protected Hashtable minmax = new Hashtable();
 +
 +  public Hashtable getMinMax()
 +  {
 +    return minmax;
 +  }
 +
 +  /**
 +   * normalise a score against the max/min bounds for the feature type.
 +   * 
 +   * @param sequenceFeature
 +   * @return byte[] { signed, normalised signed (-127 to 127) or unsigned
 +   *         (0-255) value.
 +   */
 +  protected final byte[] normaliseScore(SequenceFeature sequenceFeature)
 +  {
 +    float[] mm = ((float[][]) minmax.get(sequenceFeature.type))[0];
 +    final byte[] r = new byte[]
 +    { 0, (byte) 255 };
 +    if (mm != null)
 +    {
 +      if (r[0] != 0 || mm[0] < 0.0)
 +      {
 +        r[0] = 1;
 +        r[1] = (byte) ((int) 128.0 + 127.0 * (sequenceFeature.score / mm[1]));
 +      }
 +      else
 +      {
 +        r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1]));
 +      }
 +    }
 +    return r;
 +  }
 +
 +  boolean newFeatureAdded = false;
 +
 +  boolean findingFeatures = false;
 +
 +  protected boolean updateFeatures()
 +  {
 +    if (av.getFeaturesDisplayed() == null || renderOrder == null
 +            || newFeatureAdded)
 +    {
 +      findAllFeatures();
 +      if (av.getFeaturesDisplayed().getVisibleFeatureCount() < 1)
 +      {
 +        return false;
 +      }
 +    }
 +    // TODO: decide if we should check for the visible feature count first
 +    return true;
 +  }
 +
 +  /**
 +   * search the alignment for all new features, give them a colour and display
 +   * them. Then fires a PropertyChangeEvent on the changeSupport object.
 +   * 
 +   */
 +  protected void findAllFeatures()
 +  {
 +    synchronized (firing)
 +    {
 +      if (firing.equals(Boolean.FALSE))
 +      {
 +        firing = Boolean.TRUE;
 +        findAllFeatures(true); // add all new features as visible
 +        changeSupport.firePropertyChange("changeSupport", null, null);
 +        firing = Boolean.FALSE;
 +      }
 +    }
 +  }
 +
 +  @Override
 +  public List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
 +  {
 +    ArrayList<SequenceFeature> tmp = new ArrayList<SequenceFeature>();
 +    SequenceFeature[] features = sequence.getSequenceFeatures();
 +    if (features != null)
 +    {
 +      for (int i = 0; i < features.length; i++)
 +      {
 +        if (!av.areFeaturesDisplayed()
 +                || !av.getFeaturesDisplayed().isVisible(
 +                        features[i].getType()))
 +        {
 +          continue;
 +        }
 +
 +        if (features[i].featureGroup != null
 +                && featureGroups != null
 +                && featureGroups.containsKey(features[i].featureGroup)
-                 && !((Boolean) featureGroups.get(features[i].featureGroup))
++                && !featureGroups.get(features[i].featureGroup)
 +                        .booleanValue())
++        {
 +          continue;
++        }
 +
 +        if ((features[i].getBegin() <= res)
 +                && (features[i].getEnd() >= res))
 +        {
 +          tmp.add(features[i]);
 +        }
 +      }
 +    }
 +    return tmp;
 +  }
 +
 +  /**
 +   * Searches alignment for all features and updates colours
 +   * 
 +   * @param newMadeVisible
 +   *          if true newly added feature types will be rendered immediatly
 +   *          TODO: check to see if this method should actually be proxied so
 +   *          repaint events can be propagated by the renderer code
 +   */
 +  @Override
 +  public synchronized void findAllFeatures(boolean newMadeVisible)
 +  {
 +    newFeatureAdded = false;
 +
 +    if (findingFeatures)
 +    {
 +      newFeatureAdded = true;
 +      return;
 +    }
 +
 +    findingFeatures = true;
 +    if (av.getFeaturesDisplayed() == null)
 +    {
 +      av.setFeaturesDisplayed(new FeaturesDisplayed());
 +    }
 +    FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed();
 +
 +    ArrayList<String> allfeatures = new ArrayList<String>();
 +    ArrayList<String> oldfeatures = new ArrayList<String>();
 +    if (renderOrder != null)
 +    {
 +      for (int i = 0; i < renderOrder.length; i++)
 +      {
 +        if (renderOrder[i] != null)
 +        {
 +          oldfeatures.add(renderOrder[i]);
 +        }
 +      }
 +    }
 +    if (minmax == null)
 +    {
 +      minmax = new Hashtable();
 +    }
 +    AlignmentI alignment = av.getAlignment();
 +    for (int i = 0; i < alignment.getHeight(); i++)
 +    {
 +      SequenceI asq = alignment.getSequenceAt(i);
 +      SequenceI dasq = asq.getDatasetSequence();
 +      SequenceFeature[] features = dasq != null ? dasq
 +              .getSequenceFeatures() : asq.getSequenceFeatures();
 +
 +      if (features == null)
 +      {
 +        continue;
 +      }
 +
 +      int index = 0;
 +      while (index < features.length)
 +      {
 +        if (!featuresDisplayed.isRegistered(features[index].getType()))
 +        {
 +          String fgrp = features[index].getFeatureGroup();
 +          if (fgrp != null)
 +          {
 +            Boolean groupDisplayed = featureGroups.get(fgrp);
 +            if (groupDisplayed == null)
 +            {
 +              groupDisplayed = Boolean.valueOf(newMadeVisible);
 +              featureGroups.put(fgrp, groupDisplayed);
 +            }
-             if (!((Boolean) groupDisplayed).booleanValue())
++            if (!groupDisplayed.booleanValue())
 +            {
 +              index++;
 +              continue;
 +            }
 +          }
 +          if (!(features[index].begin == 0 && features[index].end == 0))
 +          {
 +            // If beginning and end are 0, the feature is for the whole sequence
 +            // and we don't want to render the feature in the normal way
 +
 +            if (newMadeVisible
 +                    && !oldfeatures.contains(features[index].getType()))
 +            {
 +              // this is a new feature type on the alignment. Mark it for
 +              // display.
 +              featuresDisplayed.setVisible(features[index].getType());
 +              setOrder(features[index].getType(), 0);
 +            }
 +          }
 +        }
 +        if (!allfeatures.contains(features[index].getType()))
 +        {
 +          allfeatures.add(features[index].getType());
 +        }
 +        if (features[index].score != Float.NaN)
 +        {
 +          int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
 +          float[][] mm = (float[][]) minmax.get(features[index].getType());
 +          if (mm == null)
 +          {
 +            mm = new float[][]
 +            { null, null };
 +            minmax.put(features[index].getType(), mm);
 +          }
 +          if (mm[nonpos] == null)
 +          {
 +            mm[nonpos] = new float[]
 +            { features[index].score, features[index].score };
 +
 +          }
 +          else
 +          {
 +            if (mm[nonpos][0] > features[index].score)
 +            {
 +              mm[nonpos][0] = features[index].score;
 +            }
 +            if (mm[nonpos][1] < features[index].score)
 +            {
 +              mm[nonpos][1] = features[index].score;
 +            }
 +          }
 +        }
 +        index++;
 +      }
 +    }
 +    updateRenderOrder(allfeatures);
 +    findingFeatures = false;
 +  }
 +
 +  protected Boolean firing = Boolean.FALSE;
 +
 +  /**
 +   * replaces the current renderOrder with the unordered features in
 +   * allfeatures. The ordering of any types in both renderOrder and allfeatures
 +   * is preserved, and all new feature types are rendered on top of the existing
 +   * types, in the order given by getOrder or the order given in allFeatures.
 +   * Note. this operates directly on the featureOrder hash for efficiency. TODO:
 +   * eliminate the float storage for computing/recalling the persistent ordering
 +   * New Cability: updates min/max for colourscheme range if its dynamic
 +   * 
 +   * @param allFeatures
 +   */
 +  private void updateRenderOrder(List<String> allFeatures)
 +  {
 +    List<String> allfeatures = new ArrayList<String>(allFeatures);
 +    String[] oldRender = renderOrder;
 +    renderOrder = new String[allfeatures.size()];
 +    Object mmrange, fc = null;
 +    boolean initOrders = (featureOrder == null);
 +    int opos = 0;
 +    if (oldRender != null && oldRender.length > 0)
 +    {
 +      for (int j = 0; j < oldRender.length; j++)
 +      {
 +        if (oldRender[j] != null)
 +        {
 +          if (initOrders)
 +          {
 +            setOrder(oldRender[j], (1 - (1 + (float) j)
-                     / (float) oldRender.length));
++                    / oldRender.length));
 +          }
 +          if (allfeatures.contains(oldRender[j]))
 +          {
 +            renderOrder[opos++] = oldRender[j]; // existing features always
 +            // appear below new features
 +            allfeatures.remove(oldRender[j]);
 +            if (minmax != null)
 +            {
 +              mmrange = minmax.get(oldRender[j]);
 +              if (mmrange != null)
 +              {
 +                fc = featureColours.get(oldRender[j]);
 +                if (fc != null && fc instanceof GraduatedColor
 +                        && ((GraduatedColor) fc).isAutoScale())
 +                {
 +                  ((GraduatedColor) fc).updateBounds(
 +                          ((float[][]) mmrange)[0][0],
 +                          ((float[][]) mmrange)[0][1]);
 +                }
 +              }
 +            }
 +          }
 +        }
 +      }
 +    }
 +    if (allfeatures.size() == 0)
 +    {
 +      // no new features - leave order unchanged.
 +      return;
 +    }
 +    int i = allfeatures.size() - 1;
 +    int iSize = i;
 +    boolean sort = false;
 +    String[] newf = new String[allfeatures.size()];
 +    float[] sortOrder = new float[allfeatures.size()];
 +    for (String newfeat : allfeatures)
 +    {
 +      newf[i] = newfeat;
 +      if (minmax != null)
 +      {
 +        // update from new features minmax if necessary
 +        mmrange = minmax.get(newf[i]);
 +        if (mmrange != null)
 +        {
 +          fc = featureColours.get(newf[i]);
 +          if (fc != null && fc instanceof GraduatedColor
 +                  && ((GraduatedColor) fc).isAutoScale())
 +          {
 +            ((GraduatedColor) fc).updateBounds(((float[][]) mmrange)[0][0],
 +                    ((float[][]) mmrange)[0][1]);
 +          }
 +        }
 +      }
 +      if (initOrders || !featureOrder.containsKey(newf[i]))
 +      {
 +        int denom = initOrders ? allfeatures.size() : featureOrder.size();
 +        // new unordered feature - compute persistent ordering at head of
 +        // existing features.
 +        setOrder(newf[i], i / (float) denom);
 +      }
 +      // set order from newly found feature from persisted ordering.
 +      sortOrder[i] = 2 - ((Float) featureOrder.get(newf[i])).floatValue();
 +      if (i < iSize)
 +      {
 +        // only sort if we need to
 +        sort = sort || sortOrder[i] > sortOrder[i + 1];
 +      }
 +      i--;
 +    }
 +    if (iSize > 1 && sort)
 +    {
 +      jalview.util.QuickSort.sort(sortOrder, newf);
 +    }
 +    sortOrder = null;
 +    System.arraycopy(newf, 0, renderOrder, opos, newf.length);
 +  }
 +
 +  /**
 +   * get a feature style object for the given type string. Creates a
 +   * java.awt.Color for a featureType with no existing colourscheme. TODO:
 +   * replace return type with object implementing standard abstract colour/style
 +   * interface
 +   * 
 +   * @param featureType
 +   * @return java.awt.Color or GraduatedColor
 +   */
 +  public Object getFeatureStyle(String featureType)
 +  {
 +    Object fc = featureColours.get(featureType);
 +    if (fc == null)
 +    {
 +      jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
 +      Color col = ucs.createColourFromName(featureType);
 +      featureColours.put(featureType, fc = col);
 +    }
 +    return fc;
 +  }
 +
 +  /**
 +   * return a nominal colour for this feature
 +   * 
 +   * @param featureType
 +   * @return standard color, or maximum colour for graduated colourscheme
 +   */
 +  public Color getColour(String featureType)
 +  {
 +    Object fc = getFeatureStyle(featureType);
 +
 +    if (fc instanceof Color)
 +    {
 +      return (Color) fc;
 +    }
 +    else
 +    {
 +      if (fc instanceof GraduatedColor)
 +      {
 +        return ((GraduatedColor) fc).getMaxColor();
 +      }
 +    }
 +    throw new Error("Implementation Error: Unrecognised render object "
 +            + fc.getClass() + " for features of type " + featureType);
 +  }
 +
 +  /**
 +   * calculate the render colour for a specific feature using current feature
 +   * settings.
 +   * 
 +   * @param feature
 +   * @return render colour for the given feature
 +   */
 +  public Color getColour(SequenceFeature feature)
 +  {
 +    Object fc = getFeatureStyle(feature.getType());
 +    if (fc instanceof Color)
 +    {
 +      return (Color) fc;
 +    }
 +    else
 +    {
 +      if (fc instanceof GraduatedColor)
 +      {
 +        return ((GraduatedColor) fc).findColor(feature);
 +      }
 +    }
 +    throw new Error("Implementation Error: Unrecognised render object "
 +            + fc.getClass() + " for features of type " + feature.getType());
 +  }
 +
 +  protected boolean showFeature(SequenceFeature sequenceFeature)
 +  {
 +    Object fc = getFeatureStyle(sequenceFeature.type);
 +    if (fc instanceof GraduatedColor)
 +    {
 +      return ((GraduatedColor) fc).isColored(sequenceFeature);
 +    }
 +    else
 +    {
 +      return true;
 +    }
 +  }
 +
 +  protected boolean showFeatureOfType(String type)
 +  {
 +    return av.getFeaturesDisplayed().isVisible(type);
 +  }
 +
 +  public void setColour(String featureType, Object col)
 +  {
 +    // overwrite
 +    // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
 +    // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
 +    // Object c = featureColours.get(featureType);
 +    // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
 +    // !((GraduatedColor)c).getMaxColor().equals(_col)))
 +    {
 +      featureColours.put(featureType, col);
 +    }
 +  }
 +
 +  public void setTransparency(float value)
 +  {
 +    transparency = value;
 +  }
 +
 +  public float getTransparency()
 +  {
 +    return transparency;
 +  }
 +
 +  Map featureOrder = null;
 +
 +  /**
 +   * analogous to colour - store a normalized ordering for all feature types in
 +   * this rendering context.
 +   * 
 +   * @param type
 +   *          Feature type string
 +   * @param position
 +   *          normalized priority - 0 means always appears on top, 1 means
 +   *          always last.
 +   */
 +  public float setOrder(String type, float position)
 +  {
 +    if (featureOrder == null)
 +    {
 +      featureOrder = new Hashtable();
 +    }
 +    featureOrder.put(type, new Float(position));
 +    return position;
 +  }
 +
 +  /**
 +   * get the global priority (0 (top) to 1 (bottom))
 +   * 
 +   * @param type
 +   * @return [0,1] or -1 for a type without a priority
 +   */
 +  public float getOrder(String type)
 +  {
 +    if (featureOrder != null)
 +    {
 +      if (featureOrder.containsKey(type))
 +      {
 +        return ((Float) featureOrder.get(type)).floatValue();
 +      }
 +    }
 +    return -1;
 +  }
 +
 +  @Override
-   public Map getFeatureColours()
++  public Map<String, Object> getFeatureColours()
 +  {
-     return new ConcurrentHashMap<>(featureColours);
++    return new ConcurrentHashMap<String, Object>(featureColours);
 +  }
 +
 +  /**
 +   * Replace current ordering with new ordering
 +   * 
 +   * @param data
 +   *          { String(Type), Colour(Type), Boolean(Displayed) }
 +   */
 +  public void setFeaturePriority(Object[][] data)
 +  {
 +    setFeaturePriority(data, true);
 +  }
 +
 +  /**
 +   * 
 +   * @param data
 +   *          { String(Type), Colour(Type), Boolean(Displayed) }
 +   * @param visibleNew
 +   *          when true current featureDisplay list will be cleared
 +   */
 +  public void setFeaturePriority(Object[][] data, boolean visibleNew)
 +  {
 +    FeaturesDisplayedI av_featuresdisplayed = null;
 +    if (visibleNew)
 +    {
 +      if ((av_featuresdisplayed = av.getFeaturesDisplayed()) != null)
 +      {
 +        av.getFeaturesDisplayed().clear();
 +      }
 +      else
 +      {
 +        av.setFeaturesDisplayed(av_featuresdisplayed = new FeaturesDisplayed());
 +      }
 +    }
 +    else
 +    {
 +      av_featuresdisplayed = av.getFeaturesDisplayed();
 +    }
 +    if (data == null)
 +    {
 +      return;
 +    }
 +    // The feature table will display high priority
 +    // features at the top, but theses are the ones
 +    // we need to render last, so invert the data
 +    renderOrder = new String[data.length];
 +
 +    if (data.length > 0)
 +    {
 +      for (int i = 0; i < data.length; i++)
 +      {
 +        String type = data[i][0].toString();
 +        setColour(type, data[i][1]); // todo : typesafety - feature color
 +        // interface object
 +        if (((Boolean) data[i][2]).booleanValue())
 +        {
 +          av_featuresdisplayed.setVisible(type);
 +        }
 +
 +        renderOrder[data.length - i - 1] = type;
 +      }
 +    }
 +
 +  }
 +
 +  /**
 +   * @param listener
 +   * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
 +   */
 +  public void addPropertyChangeListener(PropertyChangeListener listener)
 +  {
 +    changeSupport.addPropertyChangeListener(listener);
 +  }
 +
 +  /**
 +   * @param listener
 +   * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
 +   */
 +  public void removePropertyChangeListener(PropertyChangeListener listener)
 +  {
 +    changeSupport.removePropertyChangeListener(listener);
 +  }
 +
 +  public Set getAllFeatureColours()
 +  {
 +    return featureColours.keySet();
 +  }
 +
 +  public void clearRenderOrder()
 +  {
 +    renderOrder = null;
 +  }
 +
 +  public boolean hasRenderOrder()
 +  {
 +    return renderOrder != null;
 +  }
 +
 +  public List<String> getRenderOrder()
 +  {
 +    if (renderOrder == null)
 +    {
 +      return Arrays.asList(new String[]
 +      {});
 +    }
 +    return Arrays.asList(renderOrder);
 +  }
 +
 +  public int getFeatureGroupsSize()
 +  {
 +    return featureGroups != null ? 0 : featureGroups.size();
 +  }
 +
 +  @Override
 +  public List<String> getFeatureGroups()
 +  {
 +    // conflict between applet and desktop - featureGroups returns the map in
 +    // the desktop featureRenderer
 +    return (featureGroups == null) ? Arrays.asList(new String[0]) : Arrays
 +            .asList(featureGroups.keySet().toArray(new String[0]));
 +  }
 +
 +  public boolean checkGroupVisibility(String group, boolean newGroupsVisible)
 +  {
 +    if (featureGroups == null)
 +    {
 +      // then an exception happens next..
 +    }
 +    if (featureGroups.containsKey(group))
 +    {
-       return ((Boolean) featureGroups.get(group)).booleanValue();
++      return featureGroups.get(group).booleanValue();
 +    }
 +    if (newGroupsVisible)
 +    {
 +      featureGroups.put(group, new Boolean(true));
 +      return true;
 +    }
 +    return false;
 +  }
 +
 +  /**
 +   * get visible or invisible groups
 +   * 
 +   * @param visible
 +   *          true to return visible groups, false to return hidden ones.
 +   * @return list of groups
 +   */
 +  @Override
 +  public List getGroups(boolean visible)
 +  {
 +    if (featureGroups != null)
 +    {
 +      ArrayList gp = new ArrayList();
 +
 +      for (Object grp : featureGroups.keySet())
 +      {
-         Boolean state = (Boolean) featureGroups.get(grp);
++        Boolean state = featureGroups.get(grp);
 +        if (state.booleanValue() == visible)
 +        {
 +          gp.add(grp);
 +        }
 +      }
 +      return gp;
 +    }
 +    return null;
 +  }
 +
 +  @Override
 +  public void setGroupVisibility(String group, boolean visible)
 +  {
 +    featureGroups.put(group, new Boolean(visible));
 +  }
 +
 +  @Override
 +  public void setGroupVisibility(List<String> toset, boolean visible)
 +  {
 +    if (toset != null && toset.size() > 0 && featureGroups != null)
 +    {
 +      boolean rdrw = false;
 +      for (String gst : toset)
 +      {
 +        Boolean st = featureGroups.get(gst);
 +        featureGroups.put(gst, new Boolean(visible));
 +        if (st != null)
 +        {
-           rdrw = rdrw || (visible != ((Boolean) st).booleanValue());
++          rdrw = rdrw || (visible != st.booleanValue());
 +        }
 +      }
 +      if (rdrw)
 +      {
 +        // set local flag indicating redraw needed ?
 +      }
 +    }
 +  }
 +
 +  @Override
 +  public Hashtable getDisplayedFeatureCols()
 +  {
 +    Hashtable fcols = new Hashtable();
 +    if (getViewport().getFeaturesDisplayed() == null)
 +    {
 +      return fcols;
 +    }
 +    Iterator<String> en = getViewport().getFeaturesDisplayed()
 +            .getVisibleFeatures();
 +    while (en.hasNext())
 +    {
 +      String col = en.next();
 +      fcols.put(col, getColour(col));
 +    }
 +    return fcols;
 +  }
 +
 +  @Override
 +  public FeaturesDisplayedI getFeaturesDisplayed()
 +  {
 +    return av.getFeaturesDisplayed();
 +  }
 +
 +  @Override
 +  public String[] getDisplayedFeatureTypes()
 +  {
 +    String[] typ = null;
 +    typ = getRenderOrder().toArray(new String[0]);
 +    FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed();
 +    if (feature_disp != null)
 +    {
 +      synchronized (feature_disp)
 +      {
 +        for (int i = 0; i < typ.length; i++)
 +        {
 +          if (feature_disp.isVisible(typ[i]))
 +          {
 +            typ[i] = null;
 +          }
 +        }
 +      }
 +    }
 +    return typ;
 +  }
 +
 +  @Override
 +  public String[] getDisplayedFeatureGroups()
 +  {
 +    String[] gps = null;
 +    ArrayList<String> _gps = new ArrayList<String>();
 +    Iterator en = getFeatureGroups().iterator();
 +    int g = 0;
 +    boolean valid = false;
 +    while (en.hasNext())
 +    {
 +      String gp = (String) en.next();
 +      if (checkGroupVisibility(gp, false))
 +      {
 +        valid = true;
 +        _gps.add(gp);
 +      }
 +      if (!valid)
 +      {
 +        return null;
 +      }
 +      else
 +      {
 +        gps = new String[_gps.size()];
 +        _gps.toArray(gps);
 +      }
 +    }
 +    return gps;
 +  }
 +
 +}
@@@ -28,7 -28,8 +28,8 @@@ import jalview.datamodel.AlignmentView
  import jalview.datamodel.SequenceI;
  import jalview.gui.AlignFrame;
  import jalview.gui.WebserviceInfo;
 -import jalview.gui.FeatureRenderer.FeatureRendererSettings;
 +import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
+ import jalview.util.MessageManager;
  
  public abstract class AWSThread extends Thread
  {