91d71309c7c57ff198eb650efd0310aed15ab70a
[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.List;
39 import java.util.Vector;
40
41 import javax.swing.JCheckBoxMenuItem;
42 import javax.swing.JMenuItem;
43 import javax.swing.JOptionPane;
44
45 /**
46  * Base class with common functionality for JMol, Chimera or other structure
47  * viewers.
48  * 
49  * @author gmcarstairs
50  *
51  */
52 public abstract class StructureViewerBase extends GStructureViewer
53         implements Runnable, ViewSetProvider
54 {
55
56   /**
57    * list of sequenceSet ids associated with the view
58    */
59   protected List<String> _aps = new ArrayList<String>();
60
61   /**
62    * list of alignment panels to use for superposition
63    */
64   protected Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
65
66   /**
67    * list of alignment panels that are used for colouring structures by aligned
68    * sequences
69    */
70   protected Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
71
72   private String viewId = null;
73
74   private AlignmentPanel ap;
75
76   protected boolean alignAddedStructures = false;
77
78   protected boolean _started = false;
79
80   protected boolean addingStructures = false;
81
82   protected Thread worker = null;
83
84   protected boolean allChainsSelected = false;
85
86   /**
87    * 
88    * @param ap2
89    * @return true if this Jmol instance is linked with the given alignPanel
90    */
91   public boolean isLinkedWith(AlignmentPanel ap2)
92   {
93     return _aps.contains(ap2.av.getSequenceSetId());
94   }
95
96   public boolean isUsedforaligment(AlignmentPanel ap2)
97   {
98
99     return (_alignwith != null) && _alignwith.contains(ap2);
100   }
101
102   public boolean isUsedforcolourby(AlignmentPanel ap2)
103   {
104     return (_colourwith != null) && _colourwith.contains(ap2);
105   }
106
107   /**
108    * 
109    * @return TRUE if the view is NOT being coloured by the alignment colours.
110    */
111   public boolean isColouredByViewer()
112   {
113     return !getBinding().isColourBySequence();
114   }
115
116   public String getViewId()
117   {
118     if (viewId == null)
119     {
120       viewId = System.currentTimeMillis() + "." + this.hashCode();
121     }
122     return viewId;
123   }
124
125   protected void setViewId(String viewId)
126   {
127     this.viewId = viewId;
128   }
129
130   public abstract String getStateInfo();
131
132   protected void buildActionMenu()
133   {
134     if (_alignwith == null)
135     {
136       _alignwith = new Vector<AlignmentPanel>();
137     }
138     if (_alignwith.size() == 0 && ap != null)
139     {
140       _alignwith.add(ap);
141     }
142     ;
143     for (Component c : viewerActionMenu.getMenuComponents())
144     {
145       if (c != alignStructs)
146       {
147         viewerActionMenu.remove((JMenuItem) c);
148       }
149     }
150   }
151
152   public AlignmentPanel getAlignmentPanel()
153   {
154     return ap;
155   }
156
157   protected void setAlignmentPanel(AlignmentPanel alp)
158   {
159     this.ap = alp;
160   }
161
162   @Override
163   public AlignmentPanel[] getAllAlignmentPanels()
164   {
165     AlignmentPanel[] t, list = new AlignmentPanel[0];
166     for (String setid : _aps)
167     {
168       AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
169       if (panels != null)
170       {
171         t = new AlignmentPanel[list.length + panels.length];
172         System.arraycopy(list, 0, t, 0, list.length);
173         System.arraycopy(panels, 0, t, list.length, panels.length);
174         list = t;
175       }
176     }
177
178     return list;
179   }
180
181   /**
182    * set the primary alignmentPanel reference and add another alignPanel to the
183    * list of ones to use for colouring and aligning
184    * 
185    * @param nap
186    */
187   public void addAlignmentPanel(AlignmentPanel nap)
188   {
189     if (getAlignmentPanel() == null)
190     {
191       setAlignmentPanel(nap);
192     }
193     if (!_aps.contains(nap.av.getSequenceSetId()))
194     {
195       _aps.add(nap.av.getSequenceSetId());
196     }
197   }
198
199   /**
200    * remove any references held to the given alignment panel
201    * 
202    * @param nap
203    */
204   public void removeAlignmentPanel(AlignmentPanel nap)
205   {
206     try
207     {
208       _alignwith.remove(nap);
209       _colourwith.remove(nap);
210       if (getAlignmentPanel() == nap)
211       {
212         setAlignmentPanel(null);
213         for (AlignmentPanel aps : getAllAlignmentPanels())
214         {
215           if (aps != nap)
216           {
217             setAlignmentPanel(aps);
218             break;
219           }
220         }
221       }
222     } catch (Exception ex)
223     {
224     }
225     if (getAlignmentPanel() != null)
226     {
227       buildActionMenu();
228     }
229   }
230
231   public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
232   {
233     addAlignmentPanel(nap);
234     if (!_alignwith.contains(nap))
235     {
236       _alignwith.add(nap);
237     }
238   }
239
240   public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
241   {
242     if (_alignwith.contains(nap))
243     {
244       _alignwith.remove(nap);
245     }
246   }
247
248   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
249           boolean enableColourBySeq)
250   {
251     useAlignmentPanelForColourbyseq(nap);
252     getBinding().setColourBySequence(enableColourBySeq);
253     seqColour.setSelected(enableColourBySeq);
254     viewerColour.setSelected(!enableColourBySeq);
255   }
256
257   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
258   {
259     addAlignmentPanel(nap);
260     if (!_colourwith.contains(nap))
261     {
262       _colourwith.add(nap);
263     }
264   }
265
266   public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
267   {
268     if (_colourwith.contains(nap))
269     {
270       _colourwith.remove(nap);
271     }
272   }
273
274   public abstract ViewerType getViewerType();
275
276   protected abstract AAStructureBindingModel getBindingModel();
277
278   /**
279    * add a new structure (with associated sequences and chains) to this viewer,
280    * retrieving it if necessary first.
281    * 
282    * @param pdbentry
283    * @param seqs
284    * @param chains
285    * @param align
286    *          if true, new structure(s) will be aligned using associated
287    *          alignment
288    * @param alignFrame
289    */
290   protected void addStructure(final PDBEntry pdbentry,
291           final SequenceI[] seqs, final String[] chains,
292           final boolean align, final IProgressIndicator alignFrame)
293   {
294     if (pdbentry.getFile() == null)
295     {
296       if (worker != null && worker.isAlive())
297       {
298         // a retrieval is in progress, wait around and add ourselves to the
299         // queue.
300         new Thread(new Runnable()
301         {
302           @Override
303           public void run()
304           {
305             while (worker != null && worker.isAlive() && _started)
306             {
307               try
308               {
309                 Thread.sleep(100 + ((int) Math.random() * 100));
310
311               } catch (Exception e)
312               {
313               }
314             }
315             // and call ourselves again.
316             addStructure(pdbentry, seqs, chains, align, alignFrame);
317           }
318         }).start();
319         return;
320       }
321     }
322     // otherwise, start adding the structure.
323     getBindingModel().addSequenceAndChain(new PDBEntry[] { pdbentry },
324             new SequenceI[][] { seqs }, new String[][] { chains });
325     addingStructures = true;
326     _started = false;
327     alignAddedStructures = align;
328     worker = new Thread(this);
329     worker.start();
330     return;
331   }
332
333   /**
334    * Presents a dialog with the option to add an align a structure to an
335    * existing structure view
336    * 
337    * @param pdbId
338    * @param view
339    * @return YES, NO or CANCEL JvOptionPane code
340    */
341   protected int chooseAlignStructureToViewer(String pdbId,
342           StructureViewerBase view)
343   {
344     int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
345             MessageManager.formatMessage("label.add_pdbentry_to_view",
346                     new Object[] { pdbId, view.getTitle() }),
347             MessageManager
348                     .getString("label.align_to_existing_structure_view"),
349             JvOptionPane.YES_NO_CANCEL_OPTION);
350     return option;
351   }
352
353   protected abstract boolean hasPdbId(String pdbId);
354
355   protected abstract List<StructureViewerBase> getViewersFor(
356           AlignmentPanel alp);
357
358   /**
359    * Check for any existing views involving this alignment and give user the
360    * option to add and align this molecule to one of them
361    * 
362    * @param pdbentry
363    * @param seq
364    * @param chains
365    * @param apanel
366    * @param pdbId
367    * @return true if user adds to a view, or cancels entirely, else false
368    */
369   protected boolean addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq,
370           String[] chains, final AlignmentPanel apanel, String pdbId)
371   {
372     for (StructureViewerBase view : getViewersFor(apanel))
373     {
374       // TODO: highlight the view somehow
375       /*
376        * JAL-1742 exclude view with this structure already mapped (don't offer
377        * to align chain B to chain A of the same structure)
378        */
379       if (view.hasPdbId(pdbId))
380       {
381         continue;
382       }
383       int option = chooseAlignStructureToViewer(pdbId, view);
384       if (option == JvOptionPane.CANCEL_OPTION)
385       {
386         return true;
387       }
388       else if (option == JvOptionPane.YES_OPTION)
389       {
390         view.useAlignmentPanelForSuperposition(apanel);
391         view.addStructure(pdbentry, seq, chains, true, apanel.alignFrame);
392         return true;
393       }
394       else
395       {
396         // NO_OPTION - offer the next viewer if any
397       }
398     }
399
400     /*
401      * nothing offered and selected
402      */
403     return false;
404   }
405
406   /**
407    * Adds mappings for the given sequences to an already opened PDB structure,
408    * and updates any viewers that have the PDB file
409    * 
410    * @param seq
411    * @param chains
412    * @param apanel
413    * @param pdbFilename
414    */
415   protected void addSequenceMappingsToStructure(SequenceI[] seq,
416           String[] chains, final AlignmentPanel apanel, String pdbFilename)
417   {
418     // TODO : Fix multiple seq to one chain issue here.
419     /*
420      * create the mappings
421      */
422     apanel.getStructureSelectionManager().setMapping(seq, chains,
423             pdbFilename, DataSourceType.FILE);
424
425     /*
426      * alert the FeatureRenderer to show new (PDB RESNUM) features
427      */
428     if (apanel.getSeqPanel().seqCanvas.fr != null)
429     {
430       apanel.getSeqPanel().seqCanvas.fr.featuresAdded();
431       apanel.paintAlignment(true);
432     }
433
434     /*
435      * add the sequences to any other viewers (of the same type) for this pdb
436      * file
437      */
438     // JBPNOTE: this looks like a binding routine, rather than a gui routine
439     for (StructureViewerBase viewer : getViewersFor(null))
440     {
441       AAStructureBindingModel bindingModel = viewer.getBindingModel();
442       for (int pe = 0; pe < bindingModel.getPdbCount(); pe++)
443       {
444         if (bindingModel.getPdbEntry(pe).getFile().equals(pdbFilename))
445         {
446           bindingModel.addSequence(pe, seq);
447           viewer.addAlignmentPanel(apanel);
448           /*
449            * add it to the set of alignments used for colouring structure by
450            * sequence
451            */
452           viewer.useAlignmentPanelForColourbyseq(apanel);
453           viewer.buildActionMenu();
454           apanel.getStructureSelectionManager().sequenceColoursChanged(
455                   apanel);
456           break;
457         }
458       }
459     }
460   }
461
462   /**
463    * Check if the PDB file is already loaded, if so offer to add it to the
464    * existing viewer
465    * 
466    * @param seq
467    * @param chains
468    * @param apanel
469    * @param pdbId
470    * @return true if the user chooses to add to a viewer, or to cancel entirely
471    */
472   protected boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains,
473           final AlignmentPanel apanel, String pdbId)
474   {
475     boolean finished = false;
476     String alreadyMapped = apanel.getStructureSelectionManager()
477             .alreadyMappedToFile(pdbId);
478
479     if (alreadyMapped != null)
480     {
481       /*
482        * the PDB file is already loaded
483        */
484       int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
485               MessageManager.formatMessage(
486                       "label.pdb_entry_is_already_displayed",
487                       new Object[] { pdbId }), MessageManager
488                       .formatMessage(
489                               "label.map_sequences_to_visible_window",
490                               new Object[] { pdbId }),
491               JvOptionPane.YES_NO_CANCEL_OPTION);
492       if (option == JvOptionPane.CANCEL_OPTION)
493       {
494         finished = true;
495       }
496       else if (option == JvOptionPane.YES_OPTION)
497       {
498         addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
499         finished = true;
500       }
501     }
502     return finished;
503   }
504
505   void setChainMenuItems(List<String> chainNames)
506   {
507     chainMenu.removeAll();
508     if (chainNames == null || chainNames.isEmpty())
509     {
510       return;
511     }
512     JMenuItem menuItem = new JMenuItem(
513             MessageManager.getString("label.all"));
514     menuItem.addActionListener(new ActionListener()
515     {
516       @Override
517       public void actionPerformed(ActionEvent evt)
518       {
519         allChainsSelected = true;
520         for (int i = 0; i < chainMenu.getItemCount(); i++)
521         {
522           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
523           {
524             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
525           }
526         }
527         showSelectedChains();
528         allChainsSelected = false;
529       }
530     });
531
532     chainMenu.add(menuItem);
533
534     for (String chain : chainNames)
535     {
536       menuItem = new JCheckBoxMenuItem(chain, true);
537       menuItem.addItemListener(new ItemListener()
538       {
539         @Override
540         public void itemStateChanged(ItemEvent evt)
541         {
542           if (!allChainsSelected)
543           {
544             showSelectedChains();
545           }
546         }
547       });
548
549       chainMenu.add(menuItem);
550     }
551   }
552
553   abstract void showSelectedChains();
554
555 }