001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.nbio.structure.align.gui.jmol;
022
023import java.awt.BorderLayout;
024import java.awt.Color;
025import java.awt.Container;
026import java.awt.Dimension;
027import java.awt.event.ActionEvent;
028import java.awt.event.ActionListener;
029import java.awt.event.ItemEvent;
030import java.awt.event.ItemListener;
031import java.awt.event.WindowAdapter;
032import java.awt.event.WindowEvent;
033import java.io.StringWriter;
034import java.util.ArrayList;
035import java.util.List;
036
037import javax.swing.Box;
038import javax.swing.JButton;
039import javax.swing.JCheckBox;
040import javax.swing.JComboBox;
041import javax.swing.JFrame;
042import javax.swing.JLabel;
043import javax.swing.JMenuBar;
044import javax.swing.JPanel;
045import javax.swing.JTextField;
046
047import org.biojava.nbio.structure.Atom;
048import org.biojava.nbio.structure.PDBHeader;
049import org.biojava.nbio.structure.Structure;
050import org.biojava.nbio.structure.StructureException;
051import org.biojava.nbio.structure.StructureIdentifier;
052import org.biojava.nbio.structure.align.gui.MenuCreator;
053import org.biojava.nbio.structure.align.gui.MultipleAlignmentGUI;
054import org.biojava.nbio.structure.align.gui.MultipleAlignmentJmolDisplay;
055import org.biojava.nbio.structure.align.multiple.Block;
056import org.biojava.nbio.structure.align.multiple.BlockSet;
057import org.biojava.nbio.structure.align.multiple.MultipleAlignment;
058import org.biojava.nbio.structure.align.multiple.util.MultipleAlignmentTools;
059import org.biojava.nbio.structure.align.multiple.util.MultipleAlignmentWriter;
060import org.biojava.nbio.structure.align.webstart.AligUIManager;
061import org.biojava.nbio.structure.gui.WrapLayout;
062import org.biojava.nbio.structure.jama.Matrix;
063import org.forester.archaeopteryx.Archaeopteryx;
064import org.forester.phylogeny.Phylogeny;
065import org.jcolorbrewer.ColorBrewer;
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069/**
070 * A class that provides a 3D visualization Frame in Jmol for
071 * {@link MultipleAlignment}s.
072 *
073 * @author Aleix Lafita
074 * @since 4.1.0
075 *
076 */
077public class MultipleAlignmentJmol extends AbstractAlignmentJmol {
078
079        private MultipleAlignment multAln;
080        private List<Atom[]> transformedAtoms;
081        private JCheckBox colorByBlocks;
082        private List<JCheckBox> selectedStructures;
083
084        private static final String LIGAND_DISPLAY_SCRIPT = "select ligand; wireframe 40; spacefill 120; color CPK;";
085
086        private static final Logger logger = LoggerFactory
087                        .getLogger(MultipleAlignmentJmol.class);
088
089        /**
090         * Default constructor creates an empty JmolPanel window, from where
091         * alignments can be made through the align menu.
092         */
093        public MultipleAlignmentJmol() {
094                this(null, null);
095        }
096
097        /**
098         * The constructor displays the Mutltiple Alignment in a new JmolPanel
099         * Frame.
100         *
101         * @param msa
102         *            : contains the aligned residues.
103         * @param rotatedAtoms
104         *            : contains the transformed Atom coordinates.
105         */
106        public MultipleAlignmentJmol(MultipleAlignment msa,
107                        List<Atom[]> rotatedAtoms) {
108
109                AligUIManager.setLookAndFeel();
110                nrOpenWindows++;
111                jmolPanel = new JmolPanel();
112                frame = new JFrame();
113                JMenuBar menu = MenuCreator.initJmolMenu(frame, this, null, msa);
114
115                frame.setJMenuBar(menu);
116                this.multAln = msa;
117                this.transformedAtoms = rotatedAtoms;
118                this.selectedStructures = new ArrayList<JCheckBox>();
119
120                frame.addWindowListener(new WindowAdapter() {
121
122                        @Override
123                        public void windowClosing(WindowEvent e) {
124
125                                nrOpenWindows--;
126                                destroy();
127
128                                if (nrOpenWindows > 0)
129                                        frame.dispose();
130                                else {
131                                        MultipleAlignmentGUI gui = MultipleAlignmentGUI
132                                                        .getInstanceNoVisibilityChange();
133                                        if (gui.isVisible()) {
134                                                frame.dispose();
135                                                gui.requestFocus();
136                                        } else
137                                                System.exit(0);
138                                }
139                        }
140                });
141
142                Container contentPane = frame.getContentPane();
143
144                jmolPanel.addMouseMotionListener(this);
145                jmolPanel.addMouseListener(this);
146                jmolPanel.setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
147                contentPane.add(jmolPanel,BorderLayout.CENTER);
148
149                Box vBox = Box.createVerticalBox();
150
151                // / USER SCRIPTING COMMAND
152                JTextField field = new JTextField();
153
154                field.setMaximumSize(new Dimension(Short.MAX_VALUE, 30));
155                field.setText(COMMAND_LINE_HELP);
156                RasmolCommandListener listener = new RasmolCommandListener(jmolPanel,
157                                field);
158
159                field.addActionListener(listener);
160                field.addMouseListener(listener);
161                field.addKeyListener(listener);
162                vBox.add(field);
163
164                // / STRUCTURE SELECTION
165                if (multAln != null) {
166
167                        JPanel modelSelection = new JPanel();
168                        modelSelection.setLayout(new WrapLayout(WrapLayout.LEFT));
169                        modelSelection.setSize(new Dimension(DEFAULT_WIDTH,30));
170                        vBox.add(modelSelection);
171                        
172                        JButton show = new JButton("Show Only: ");
173                        show.addActionListener(new ActionListener() {
174
175                                @Override
176                                public void actionPerformed(ActionEvent e) {
177                                        jmolPanel.evalString("save selection;");
178                                        String cmd = getJmolString(multAln,
179                                                        transformedAtoms, colorPalette,
180                                                        colorByBlocks.isSelected());
181                                        cmd += "; restrict ";
182                                        for (int st = 0; st < multAln.size(); st++) {
183                                                if (selectedStructures.get(st).isSelected()) {
184                                                        cmd += "*/" + (st + 1) + ", ";
185                                                }
186                                        }
187                                        cmd += "none;";
188                                        jmolPanel.executeCmd(cmd + " restore selection;");
189                                }
190                        });
191                        modelSelection.add(show);
192                
193                        // Check boxes for all models
194                        for(int str = 0; str < multAln.size();str++) {
195                                JCheckBox structureSelection = new JCheckBox(multAln
196                                                .getEnsemble().getStructureIdentifiers().get(str)
197                                                .getIdentifier());
198                                modelSelection.add(structureSelection);
199                                structureSelection.setSelected(true);
200                                selectedStructures.add(structureSelection);
201                        }
202                }
203
204                // / COMBO BOXES
205                Box hBox1 = Box.createHorizontalBox();
206                hBox1.add(Box.createGlue());
207
208                String[] styles = new String[] { "Cartoon", "Backbone", "CPK",
209                                "Ball and Stick", "Ligands", "Ligands and Pocket" };
210                JComboBox<String> style = new JComboBox<>(styles);
211
212                hBox1.setMaximumSize(new Dimension(Short.MAX_VALUE, 30));
213
214                hBox1.add(new JLabel("Style"));
215                hBox1.add(style);
216                vBox.add(hBox1);
217
218                style.addActionListener(jmolPanel);
219
220                String[] colorModes = new String[] { "Secondary Structure", "By Chain",
221                                "Rainbow", "By Element", "By Amino Acid", "Hydrophobicity",
222                                "Suggest Domains", "Show SCOP Domains" };
223                JComboBox<String> jcolors = new JComboBox<>(colorModes);
224                jcolors.addActionListener(jmolPanel);
225
226                hBox1.add(Box.createGlue());
227                hBox1.add(new JLabel("Color"));
228                hBox1.add(jcolors);
229
230                String[] cPalette = { "Spectral", "Set1", "Set2", "Pastel" };
231                JComboBox<String> palette = new JComboBox<>(cPalette);
232
233                palette.addActionListener(new ActionListener() {
234                        @Override
235                        public void actionPerformed(ActionEvent e) {
236
237                                @SuppressWarnings("unchecked")
238                                JComboBox<String> source = (JComboBox<String>) e.getSource();
239                                String value = source.getSelectedItem().toString();
240                                evalString("save selection; select *; color grey; "
241                                                + "select ligand; color CPK;");
242
243                                if (value == "Set1") {
244                                        colorPalette = ColorBrewer.Set1;
245                                } else if (value == "Set2") {
246                                        colorPalette = ColorBrewer.Set2;
247                                } else if (value == "Spectral") {
248                                        colorPalette = ColorBrewer.Spectral;
249                                } else if (value == "Pastel") {
250                                        colorPalette = ColorBrewer.Pastel1;
251                                }
252                                String script = getJmolString(multAln, transformedAtoms,
253                                                colorPalette, colorByBlocks.isSelected());
254                                evalString(script + "; restore selection; ");
255                        }
256                });
257
258                hBox1.add(Box.createGlue());
259                hBox1.add(new JLabel("Palette"));
260                hBox1.add(palette);
261
262                // / CHECK BOXES
263                Box hBox2 = Box.createHorizontalBox();
264                hBox2.setMaximumSize(new Dimension(Short.MAX_VALUE, 30));
265
266                JButton resetDisplay = new JButton("Reset Display");
267                resetDisplay.addActionListener(new ActionListener() {
268
269                        @Override
270                        public void actionPerformed(ActionEvent e) {
271                                logger.info("reset!!");
272                                jmolPanel.executeCmd("restore STATE state_1");
273
274                        }
275                });
276
277                hBox2.add(resetDisplay);
278                hBox2.add(Box.createGlue());
279
280                JCheckBox toggleSelection = new JCheckBox("Show Selection");
281                toggleSelection.addItemListener(new ItemListener() {
282
283                        @Override
284                        public void itemStateChanged(ItemEvent e) {
285                                boolean showSelection = (e.getStateChange() == ItemEvent.SELECTED);
286
287                                if (showSelection) {
288                                        jmolPanel.executeCmd("set display selected");
289                                } else
290                                        jmolPanel.executeCmd("set display off");
291                        }
292                });
293                hBox2.add(toggleSelection);
294                hBox2.add(Box.createGlue());
295
296                colorByBlocks = new JCheckBox("Color By Block");
297                colorByBlocks.addItemListener(new ItemListener() {
298                        @Override
299                        public void itemStateChanged(ItemEvent e) {
300                                evalString("save selection; "
301                                                + getJmolString(multAln, transformedAtoms,
302                                                                colorPalette, colorByBlocks.isSelected())
303                                                + "; restore selection;");
304                        }
305                });
306
307                hBox2.add(colorByBlocks);
308                hBox2.add(Box.createGlue());
309
310                vBox.add(hBox2);
311
312                // STATUS DISPLAY
313                Box hBox = Box.createHorizontalBox();
314
315                status = new JTextField();
316                status.setBackground(Color.white);
317                status.setEditable(false);
318                status.setMaximumSize(new Dimension(Short.MAX_VALUE, 30));
319                status.setPreferredSize(new Dimension(DEFAULT_WIDTH / 2, 30));
320                status.setMinimumSize(new Dimension(DEFAULT_WIDTH / 2, 30));
321                hBox.add(status);
322
323                text = new JTextField();
324                text.setBackground(Color.white);
325                text.setMaximumSize(new Dimension(Short.MAX_VALUE, 30));
326                text.setPreferredSize(new Dimension(DEFAULT_WIDTH / 2, 30));
327                text.setMinimumSize(new Dimension(DEFAULT_WIDTH / 2, 30));
328                text.setText("Display of Atom info");
329                text.setEditable(false);
330                hBox.add(text);
331
332                vBox.add(hBox);
333
334                contentPane.add(vBox,BorderLayout.SOUTH);
335                MyJmolStatusListener li = (MyJmolStatusListener) jmolPanel
336                                .getStatusListener();
337
338                li.setTextField(status);
339                frame.pack();
340                frame.setVisible(true);
341
342                initCoords();
343                resetDisplay();
344        }
345
346        @Override
347        protected void initCoords() {
348                try {
349                        if (multAln == null) {
350                                if (structure != null)
351                                        setStructure(structure);
352                                else {
353                                        logger.error("Could not find anything to display!");
354                                        return;
355                                }
356                        }
357                        PDBHeader header = new PDBHeader();
358                        String title = multAln.getEnsemble().getAlgorithmName() + " V."
359                                        + multAln.getEnsemble().getVersion() + " : ";
360
361                        for (StructureIdentifier name : multAln.getEnsemble()
362                                        .getStructureIdentifiers()) {
363                                title += name.getIdentifier() + " ";
364                        }
365                        Structure artificial = MultipleAlignmentJmolDisplay
366                                        .getAlignedStructure(transformedAtoms);
367
368                        artificial.setPDBHeader(header);
369                        setStructure(artificial);
370                        header.setTitle(title);
371                        logger.info(title);
372
373                } catch (StructureException e) {
374                        e.printStackTrace();
375                }
376        }
377
378        @Override
379        public void destroy() {
380                super.destroy();
381                multAln = null;
382                transformedAtoms = null;
383        }
384
385        @Override
386        public void actionPerformed(ActionEvent ae) {
387                String cmd = ae.getActionCommand();
388                if (multAln == null) {
389                        logger.error("Currently not viewing an alignment!");
390                        return;
391                }
392                try {
393                        if (cmd.equals(MenuCreator.TEXT_ONLY)) {
394                                logger.warn("Option not available for MultipleAlignments");
395
396                        } else if (cmd.equals(MenuCreator.PAIRS_ONLY)) {
397                                String result = MultipleAlignmentWriter
398                                                .toAlignedResidues(multAln);
399                                MultipleAlignmentJmolDisplay
400                                                .showAlignmentImage(multAln, result);
401
402                        } else if (cmd.equals(MenuCreator.ALIGNMENT_PANEL)) {
403                                MultipleAlignmentJmolDisplay.showMultipleAligmentPanel(multAln,
404                                                this);
405
406                        } else if (cmd.equals(MenuCreator.FATCAT_TEXT)) {
407                                String result = MultipleAlignmentWriter.toFatCat(multAln)
408                                                + "\n";
409                                result += MultipleAlignmentWriter.toTransformMatrices(multAln);
410                                MultipleAlignmentJmolDisplay
411                                                .showAlignmentImage(multAln, result);
412
413                        } else if (cmd.equals(MenuCreator.PHYLOGENETIC_TREE)) {
414
415                                // Kimura, Structural and Fractional Dissimilarity Score
416                                Phylogeny kimura = MultipleAlignmentTools
417                                                .getKimuraTree(multAln);
418                                Phylogeny sdm = MultipleAlignmentTools.getHSDMTree(multAln);
419                                // Phylogeny structural = MultipleAlignmentTools
420                                // .getStructuralTree(multAln);
421
422                                Archaeopteryx
423                                                .createApplication(new Phylogeny[] { kimura, sdm });
424                        }
425                } catch (Exception e) {
426                        logger.error("Could not complete display option.", e);
427                }
428        }
429
430        /**
431         * Generate a Jmol command String that colors the aligned residues of every
432         * structure.
433         */
434        public static String getJmolString(MultipleAlignment multAln,
435                        List<Atom[]> transformedAtoms, ColorBrewer colorPalette,
436                        boolean colorByBlocks) {
437
438                // Color by blocks if specified
439                if (colorByBlocks)
440                        return getMultiBlockJmolString(multAln, transformedAtoms,
441                                        colorPalette, colorByBlocks);
442                Color[] colors = colorPalette.getColorPalette(multAln.size());
443
444                StringBuffer j = new StringBuffer();
445                j.append(DEFAULT_SCRIPT);
446
447                // Color the equivalent residues of every structure
448                StringBuffer sel = new StringBuffer();
449                sel.append("select *; color lightgrey; backbone 0.1; ");
450                List<List<String>> allPDB = new ArrayList<List<String>>();
451
452                // Get the aligned residues of every structure
453                for (int i = 0; i < multAln.size(); i++) {
454
455                        List<String> pdb = MultipleAlignmentJmolDisplay.getPDBresnum(i,
456                                        multAln, transformedAtoms.get(i));
457
458                        allPDB.add(pdb);
459                        sel.append("select ");
460                        int pos = 0;
461                        for (String res : pdb) {
462                                if (pos > 0)
463                                        sel.append(",");
464                                pos++;
465
466                                sel.append(res);
467                                sel.append("/" + (i + 1));
468                        }
469                        if (pos == 0)
470                                sel.append("none");
471                        sel.append("; backbone 0.3 ; color [" + colors[i].getRed() + ","
472                                        + colors[i].getGreen() + "," + colors[i].getBlue() + "]; ");
473                }
474
475                j.append(sel);
476                j.append("model 0;  ");
477                j.append(LIGAND_DISPLAY_SCRIPT);
478
479                return j.toString();
480        }
481
482        /**
483         * Colors every Block of the structures with a different color, following
484         * the palette. It colors each Block differently, no matter if it is from
485         * the same or different BlockSet.
486         */
487        public static String getMultiBlockJmolString(MultipleAlignment multAln,
488                        List<Atom[]> transformedAtoms, ColorBrewer colorPalette,
489                        boolean colorByBlocks) {
490
491                StringWriter jmol = new StringWriter();
492                jmol.append(DEFAULT_SCRIPT);
493                jmol.append("select *; color lightgrey; backbone 0.1; ");
494
495                int blockNum = multAln.getBlocks().size();
496                Color[] colors = colorPalette.getColorPalette(blockNum);
497
498                // For every structure color all the blocks with the printBlock method
499                for (int str = 0; str < transformedAtoms.size(); str++) {
500                        jmol.append("select */" + (str + 1) + "; color lightgrey; model "
501                                        + (str + 1) + "; ");
502
503                        int index = 0;
504                        for (BlockSet bs : multAln.getBlockSets()) {
505                                for (Block b : bs.getBlocks()) {
506
507                                        List<List<Integer>> alignRes = b.getAlignRes();
508                                        printJmolScript4Block(transformedAtoms.get(str), alignRes,
509                                                        colors[index], jmol, str, index, blockNum);
510                                        index++;
511                                }
512                        }
513                }
514
515                jmol.append("model 0;  ");
516                jmol.append(LIGAND_DISPLAY_SCRIPT);
517
518                return jmol.toString();
519        }
520
521        private static void printJmolScript4Block(Atom[] atoms,
522                        List<List<Integer>> alignRes, Color blockColor, StringWriter jmol,
523                        int str, int colorPos, int blockNum) {
524
525                // Obtain the residues aligned in this block of the structure
526                List<String> pdb = new ArrayList<String>();
527                for (int i = 0; i < alignRes.get(str).size(); i++) {
528
529                        // Handle gaps - only color if it is not null
530                        if (alignRes.get(str).get(i) != null) {
531                                int pos = alignRes.get(str).get(i);
532                                pdb.add(JmolTools.getPdbInfo(atoms[pos]));
533                        }
534                }
535
536                // Select the aligned residues
537                StringBuffer buf = new StringBuffer("select ");
538                int count = 0;
539                for (String res : pdb) {
540                        if (count > 0)
541                                buf.append(",");
542                        buf.append(res);
543                        buf.append("/" + (str + 1));
544                        count++;
545                }
546
547                buf.append("; backbone 0.3 ; color [" + blockColor.getRed() + ","
548                                + blockColor.getGreen() + "," + blockColor.getBlue() + "]; ");
549
550                jmol.append(buf);
551        }
552
553        @Override
554        public void resetDisplay() {
555
556                if (multAln != null && transformedAtoms != null) {
557                        String script = getJmolString(multAln, transformedAtoms,
558                                        colorPalette, colorByBlocks.isSelected());
559                        logger.debug(script);
560                        evalString(script);
561                        jmolPanel.evalString("save STATE state_1");
562                }
563        }
564
565        @Override
566        public List<Matrix> getDistanceMatrices() {
567                if (multAln == null)
568                        return null;
569                else
570                        return multAln.getEnsemble().getDistanceMatrix();
571        }
572
573        public void setColorByBlocks(boolean colorByBlocks) {
574                this.colorByBlocks.setSelected(colorByBlocks);
575                resetDisplay();
576        }
577
578        public JFrame getFrame() {
579                return frame;
580        }
581
582        public MultipleAlignment getMultipleAlignment() {
583                return multAln;
584        }
585
586}