d7d01ca7085f531fe2d487a89aed906dbe32239e
[jalview.git] / src / jalview / gui / StructureViewerBase.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.datamodel.PDBEntry;
24 import jalview.datamodel.SequenceI;
25 import jalview.gui.StructureViewer.ViewerType;
26 import jalview.gui.ViewSelectionMenu.ViewSetProvider;
27 import jalview.io.DataSourceType;
28 import jalview.jbgui.GStructureViewer;
29 import jalview.structures.models.AAStructureBindingModel;
30 import jalview.util.MessageManager;
31
32 import java.awt.Component;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.ItemEvent;
36 import java.awt.event.ItemListener;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Vector;
41
42 import javax.swing.JCheckBoxMenuItem;
43 import javax.swing.JMenu;
44 import javax.swing.JMenuItem;
45
46 /**
47  * Base class with common functionality for JMol, Chimera or other structure
48  * viewers.
49  * 
50  * @author gmcarstairs
51  *
52  */
53 public abstract class StructureViewerBase extends GStructureViewer
54         implements Runnable, ViewSetProvider
55 {
56   /*
57    * prefix for attributes on structure which are derived from Jalview features
58    */
59   public static final String NAMESPACE_PREFIX = "jv_";
60
61   /**
62    * list of sequenceSet ids associated with the view
63    */
64   protected List<String> _aps = new ArrayList<String>();
65
66   /**
67    * list of alignment panels to use for superposition
68    */
69   protected Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
70
71   /**
72    * list of alignment panels that are used for colouring structures by aligned
73    * sequences
74    */
75   protected Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
76
77   private String viewId = null;
78
79   private AlignmentPanel ap;
80
81   protected boolean alignAddedStructures = false;
82
83   protected boolean _started = false;
84
85   protected boolean addingStructures = false;
86
87   protected Thread worker = null;
88
89   protected boolean allChainsSelected = false;
90
91   /**
92    * 
93    * @param ap2
94    * @return true if this Jmol instance is linked with the given alignPanel
95    */
96   public boolean isLinkedWith(AlignmentPanel ap2)
97   {
98     return _aps.contains(ap2.av.getSequenceSetId());
99   }
100
101   public boolean isUsedforaligment(AlignmentPanel ap2)
102   {
103
104     return (_alignwith != null) && _alignwith.contains(ap2);
105   }
106
107   public boolean isUsedforcolourby(AlignmentPanel ap2)
108   {
109     return (_colourwith != null) && _colourwith.contains(ap2);
110   }
111
112   /**
113    * 
114    * @return TRUE if the view is NOT being coloured by the alignment colours.
115    */
116   public boolean isColouredByViewer()
117   {
118     return !getBinding().isColourBySequence();
119   }
120
121   public String getViewId()
122   {
123     if (viewId == null)
124     {
125       viewId = System.currentTimeMillis() + "." + this.hashCode();
126     }
127     return viewId;
128   }
129
130   protected void setViewId(String viewId)
131   {
132     this.viewId = viewId;
133   }
134
135   public abstract String getStateInfo();
136
137   protected void buildActionMenu()
138   {
139     if (_alignwith == null)
140     {
141       _alignwith = new Vector<AlignmentPanel>();
142     }
143     if (_alignwith.size() == 0 && ap != null)
144     {
145       _alignwith.add(ap);
146     }
147     ;
148     for (Component c : viewerActionMenu.getMenuComponents())
149     {
150       if (c != alignStructs)
151       {
152         viewerActionMenu.remove((JMenuItem) c);
153       }
154     }
155   }
156
157   public AlignmentPanel getAlignmentPanel()
158   {
159     return ap;
160   }
161
162   protected void setAlignmentPanel(AlignmentPanel alp)
163   {
164     this.ap = alp;
165   }
166
167   @Override
168   public AlignmentPanel[] getAllAlignmentPanels()
169   {
170     AlignmentPanel[] t, list = new AlignmentPanel[0];
171     for (String setid : _aps)
172     {
173       AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
174       if (panels != null)
175       {
176         t = new AlignmentPanel[list.length + panels.length];
177         System.arraycopy(list, 0, t, 0, list.length);
178         System.arraycopy(panels, 0, t, list.length, panels.length);
179         list = t;
180       }
181     }
182
183     return list;
184   }
185
186   /**
187    * set the primary alignmentPanel reference and add another alignPanel to the
188    * list of ones to use for colouring and aligning
189    * 
190    * @param nap
191    */
192   public void addAlignmentPanel(AlignmentPanel nap)
193   {
194     if (getAlignmentPanel() == null)
195     {
196       setAlignmentPanel(nap);
197     }
198     if (!_aps.contains(nap.av.getSequenceSetId()))
199     {
200       _aps.add(nap.av.getSequenceSetId());
201     }
202   }
203
204   /**
205    * remove any references held to the given alignment panel
206    * 
207    * @param nap
208    */
209   public void removeAlignmentPanel(AlignmentPanel nap)
210   {
211     try
212     {
213       _alignwith.remove(nap);
214       _colourwith.remove(nap);
215       if (getAlignmentPanel() == nap)
216       {
217         setAlignmentPanel(null);
218         for (AlignmentPanel aps : getAllAlignmentPanels())
219         {
220           if (aps != nap)
221           {
222             setAlignmentPanel(aps);
223             break;
224           }
225         }
226       }
227     } catch (Exception ex)
228     {
229     }
230     if (getAlignmentPanel() != null)
231     {
232       buildActionMenu();
233     }
234   }
235
236   public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
237   {
238     addAlignmentPanel(nap);
239     if (!_alignwith.contains(nap))
240     {
241       _alignwith.add(nap);
242     }
243   }
244
245   public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
246   {
247     if (_alignwith.contains(nap))
248     {
249       _alignwith.remove(nap);
250     }
251   }
252
253   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
254           boolean enableColourBySeq)
255   {
256     useAlignmentPanelForColourbyseq(nap);
257     getBinding().setColourBySequence(enableColourBySeq);
258     seqColour.setSelected(enableColourBySeq);
259     viewerColour.setSelected(!enableColourBySeq);
260   }
261
262   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
263   {
264     addAlignmentPanel(nap);
265     if (!_colourwith.contains(nap))
266     {
267       _colourwith.add(nap);
268     }
269   }
270
271   public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
272   {
273     if (_colourwith.contains(nap))
274     {
275       _colourwith.remove(nap);
276     }
277   }
278
279   public abstract ViewerType getViewerType();
280
281   protected abstract AAStructureBindingModel getBindingModel();
282
283   /**
284    * add a new structure (with associated sequences and chains) to this viewer,
285    * retrieving it if necessary first.
286    * 
287    * @param pdbentry
288    * @param seqs
289    * @param chains
290    * @param align
291    *          if true, new structure(s) will be aligned using associated
292    *          alignment
293    * @param alignFrame
294    */
295   protected void addStructure(final PDBEntry pdbentry,
296           final SequenceI[] seqs, final String[] chains,
297           final boolean align, final IProgressIndicator alignFrame)
298   {
299     if (pdbentry.getFile() == null)
300     {
301       if (worker != null && worker.isAlive())
302       {
303         // a retrieval is in progress, wait around and add ourselves to the
304         // queue.
305         new Thread(new Runnable()
306         {
307           @Override
308           public void run()
309           {
310             while (worker != null && worker.isAlive() && _started)
311             {
312               try
313               {
314                 Thread.sleep(100 + ((int) Math.random() * 100));
315
316               } catch (Exception e)
317               {
318               }
319             }
320             // and call ourselves again.
321             addStructure(pdbentry, seqs, chains, align, alignFrame);
322           }
323         }).start();
324         return;
325       }
326     }
327     // otherwise, start adding the structure.
328     getBindingModel().addSequenceAndChain(new PDBEntry[] { pdbentry },
329             new SequenceI[][] { seqs }, new String[][] { chains });
330     addingStructures = true;
331     _started = false;
332     alignAddedStructures = align;
333     worker = new Thread(this);
334     worker.start();
335     return;
336   }
337
338   /**
339    * Presents a dialog with the option to add an align a structure to an
340    * existing structure view
341    * 
342    * @param pdbId
343    * @param view
344    * @return YES, NO or CANCEL JvOptionPane code
345    */
346   protected int chooseAlignStructureToViewer(String pdbId,
347           StructureViewerBase view)
348   {
349     int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
350             MessageManager.formatMessage("label.add_pdbentry_to_view",
351                     new Object[] { pdbId, view.getTitle() }),
352             MessageManager
353                     .getString("label.align_to_existing_structure_view"),
354             JvOptionPane.YES_NO_CANCEL_OPTION);
355     return option;
356   }
357
358   protected abstract boolean hasPdbId(String pdbId);
359
360   protected abstract List<StructureViewerBase> getViewersFor(
361           AlignmentPanel alp);
362
363   /**
364    * Check for any existing views involving this alignment and give user the
365    * option to add and align this molecule to one of them
366    * 
367    * @param pdbentry
368    * @param seq
369    * @param chains
370    * @param apanel
371    * @param pdbId
372    * @return true if user adds to a view, or cancels entirely, else false
373    */
374   protected boolean addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq,
375           String[] chains, final AlignmentPanel apanel, String pdbId)
376   {
377     for (StructureViewerBase view : getViewersFor(apanel))
378     {
379       // TODO: highlight the view somehow
380       /*
381        * JAL-1742 exclude view with this structure already mapped (don't offer
382        * to align chain B to chain A of the same structure)
383        */
384       if (view.hasPdbId(pdbId))
385       {
386         continue;
387       }
388       int option = chooseAlignStructureToViewer(pdbId, view);
389       if (option == JvOptionPane.CANCEL_OPTION)
390       {
391         return true;
392       }
393       else if (option == JvOptionPane.YES_OPTION)
394       {
395         view.useAlignmentPanelForSuperposition(apanel);
396         view.addStructure(pdbentry, seq, chains, true, apanel.alignFrame);
397         return true;
398       }
399       else
400       {
401         // NO_OPTION - offer the next viewer if any
402       }
403     }
404
405     /*
406      * nothing offered and selected
407      */
408     return false;
409   }
410
411   /**
412    * Adds mappings for the given sequences to an already opened PDB structure,
413    * and updates any viewers that have the PDB file
414    * 
415    * @param seq
416    * @param chains
417    * @param apanel
418    * @param pdbFilename
419    */
420   protected void addSequenceMappingsToStructure(SequenceI[] seq,
421           String[] chains, final AlignmentPanel apanel, String pdbFilename)
422   {
423     // TODO : Fix multiple seq to one chain issue here.
424     /*
425      * create the mappings
426      */
427     apanel.getStructureSelectionManager().setMapping(seq, chains,
428             pdbFilename, DataSourceType.FILE);
429
430     /*
431      * alert the FeatureRenderer to show new (PDB RESNUM) features
432      */
433     if (apanel.getSeqPanel().seqCanvas.fr != null)
434     {
435       apanel.getSeqPanel().seqCanvas.fr.featuresAdded();
436       apanel.paintAlignment(true);
437     }
438
439     /*
440      * add the sequences to any other viewers (of the same type) for this pdb
441      * file
442      */
443     // JBPNOTE: this looks like a binding routine, rather than a gui routine
444     for (StructureViewerBase viewer : getViewersFor(null))
445     {
446       AAStructureBindingModel bindingModel = viewer.getBindingModel();
447       for (int pe = 0; pe < bindingModel.getPdbCount(); pe++)
448       {
449         if (bindingModel.getPdbEntry(pe).getFile().equals(pdbFilename))
450         {
451           bindingModel.addSequence(pe, seq);
452           viewer.addAlignmentPanel(apanel);
453           /*
454            * add it to the set of alignments used for colouring structure by
455            * sequence
456            */
457           viewer.useAlignmentPanelForColourbyseq(apanel);
458           viewer.buildActionMenu();
459           apanel.getStructureSelectionManager().sequenceColoursChanged(
460                   apanel);
461           break;
462         }
463       }
464     }
465   }
466
467   /**
468    * Check if the PDB file is already loaded, if so offer to add it to the
469    * existing viewer
470    * 
471    * @param seq
472    * @param chains
473    * @param apanel
474    * @param pdbId
475    * @return true if the user chooses to add to a viewer, or to cancel entirely
476    */
477   protected boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains,
478           final AlignmentPanel apanel, String pdbId)
479   {
480     boolean finished = false;
481     String alreadyMapped = apanel.getStructureSelectionManager()
482             .alreadyMappedToFile(pdbId);
483
484     if (alreadyMapped != null)
485     {
486       /*
487        * the PDB file is already loaded
488        */
489       int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
490               MessageManager.formatMessage(
491                       "label.pdb_entry_is_already_displayed",
492                       new Object[] { pdbId }), MessageManager
493                       .formatMessage(
494                               "label.map_sequences_to_visible_window",
495                               new Object[] { pdbId }),
496               JvOptionPane.YES_NO_CANCEL_OPTION);
497       if (option == JvOptionPane.CANCEL_OPTION)
498       {
499         finished = true;
500       }
501       else if (option == JvOptionPane.YES_OPTION)
502       {
503         addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
504         finished = true;
505       }
506     }
507     return finished;
508   }
509
510   void setChainMenuItems(List<String> chainNames)
511   {
512     chainMenu.removeAll();
513     if (chainNames == null || chainNames.isEmpty())
514     {
515       return;
516     }
517     JMenuItem menuItem = new JMenuItem(
518             MessageManager.getString("label.all"));
519     menuItem.addActionListener(new ActionListener()
520     {
521       @Override
522       public void actionPerformed(ActionEvent evt)
523       {
524         allChainsSelected = true;
525         for (int i = 0; i < chainMenu.getItemCount(); i++)
526         {
527           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
528           {
529             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
530           }
531         }
532         showSelectedChains();
533         allChainsSelected = false;
534       }
535     });
536
537     chainMenu.add(menuItem);
538
539     for (String chain : chainNames)
540     {
541       menuItem = new JCheckBoxMenuItem(chain, true);
542       menuItem.addItemListener(new ItemListener()
543       {
544         @Override
545         public void itemStateChanged(ItemEvent evt)
546         {
547           if (!allChainsSelected)
548           {
549             showSelectedChains();
550           }
551         }
552       });
553
554       chainMenu.add(menuItem);
555     }
556   }
557
558   abstract void showSelectedChains();
559
560   /**
561    * Send a command to the structure viewer to create residue attributes for
562    * Jalview features
563    */
564   abstract protected void sendFeaturesToViewer();
565
566   /**
567    * Query the structure viewer for its residue attribute names and add them as
568    * sub-items of the attributes menu. Names of the form "jv_*" are ignored as
569    * these originated from Jalview so no need to copy them back.
570    * 
571    * @param attributesMenu
572    */
573   protected void buildAttributesMenu(JMenu attributesMenu)
574   {
575     List<String> atts = getResidueAttributeNames();
576     if (atts == null)
577     {
578       return;
579     }
580     attributesMenu.removeAll();
581     Collections.sort(atts);
582     for (final String att : atts)
583     {
584       /*
585        * ignore 'jv_*' attributes, as these are Jalview features that have
586        * been transferred to residue attributes in Chimera!
587        */
588       if (!att.startsWith(StructureViewerBase.NAMESPACE_PREFIX))
589       {
590         JMenuItem menuItem = new JMenuItem(att);
591         menuItem.addActionListener(new ActionListener()
592         {
593           @Override
594           public void actionPerformed(ActionEvent e)
595           {
596             getResidueAttributes(att);
597           }
598         });
599         attributesMenu.add(menuItem);
600       }
601     }
602   }
603
604   /**
605    * Queries the structure viewer for residues with the given attribute, and
606    * creates sequence features in Jalview for the corresponding mapped positions
607    * 
608    * @param attName
609    */
610   protected abstract void getResidueAttributes(String attName);
611
612   /**
613    * Returns a list of residue attributes in the structure viewer, excluding any
614    * with prefix "jv_"
615    * 
616    * @return
617    */
618   protected abstract List<String> getResidueAttributeNames();
619
620 }