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