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