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