From: Jim Procter Date: Fri, 14 Nov 2014 12:16:32 +0000 (+0000) Subject: Merge branch 'develop' into JAL-1483_featureBasedTreeCalc X-Git-Tag: Jalview_2_9~123^2~11 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;ds=sidebyside;h=497958b4e5217efaa3ddeece38f38c3a6e98cb96;p=jalview.git Merge branch 'develop' into JAL-1483_featureBasedTreeCalc Conflicts: src/jalview/api/AlignViewportI.java src/jalview/appletgui/FeatureRenderer.java src/jalview/gui/FeatureRenderer.java src/jalview/gui/Jalview2XML.java src/jalview/gui/Jalview2XML_V1.java src/jalview/ws/AWSThread.java --- 497958b4e5217efaa3ddeece38f38c3a6e98cb96 diff --cc src/jalview/api/AlignViewportI.java index a97f464,d8ba30d..24ff7a6 --- a/src/jalview/api/AlignViewportI.java +++ b/src/jalview/api/AlignViewportI.java @@@ -191,12 -192,12 +192,20 @@@ public interface AlignViewport void setConservation(Conservation cons); + /** + * get a copy of the currently visible alignment annotation + * @param selectedOnly if true - trim to selected regions on the alignment + * @return an empty list or new alignment annotation objects shown only visible columns trimmed to selected region only + */ + List getVisibleAlignmentAnnotation( + boolean selectedOnly); + + FeaturesDisplayedI getFeaturesDisplayed(); + + String getSequenceSetId(); + + boolean isShowSequenceFeatures(); + + void setShowSequenceFeatures(boolean b); + } diff --cc src/jalview/gui/AlignViewport.java index a37ded9,5c383d8..d6df385 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@@ -95,8 -96,12 +96,10 @@@ public class AlignViewport extends Alig boolean renderGaps = true; - boolean showSequenceFeatures = false; - boolean showAnnotation = true; + SequenceAnnotationOrder sortAnnotationsBy = null; + int charHeight; int charWidth; diff --cc src/jalview/gui/ChimeraViewFrame.java index 0000000,3c53762..75aed5d mode 000000,100644..100644 --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@@ -1,0 -1,1245 +1,1245 @@@ + /* + * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2) + * Copyright (C) 2014 The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ + package jalview.gui; + + import jalview.api.SequenceStructureBinding; + import jalview.api.structures.JalviewStructureDisplayI; + import jalview.bin.Cache; + import jalview.datamodel.Alignment; + import jalview.datamodel.AlignmentI; + import jalview.datamodel.ColumnSelection; + import jalview.datamodel.PDBEntry; + import jalview.datamodel.SequenceI; + import jalview.gui.ViewSelectionMenu.ViewSetProvider; + import jalview.io.AppletFormatAdapter; + import jalview.io.JalviewFileChooser; + import jalview.io.JalviewFileView; + import jalview.jbgui.GStructureViewer; + import jalview.schemes.BuriedColourScheme; + import jalview.schemes.ColourSchemeI; + import jalview.schemes.HelixColourScheme; + import jalview.schemes.HydrophobicColourScheme; + import jalview.schemes.PurinePyrimidineColourScheme; + import jalview.schemes.StrandColourScheme; + import jalview.schemes.TaylorColourScheme; + import jalview.schemes.TurnColourScheme; + import jalview.schemes.ZappoColourScheme; + import jalview.util.MessageManager; + import jalview.util.Platform; + import jalview.ws.dbsources.Pdb; + + import java.awt.Component; + import java.awt.event.ActionEvent; + import java.awt.event.ActionListener; + import java.awt.event.ItemEvent; + import java.awt.event.ItemListener; + import java.io.BufferedReader; + import java.io.File; + import java.io.FileOutputStream; + import java.io.FileReader; + import java.io.IOException; + import java.io.PrintWriter; + import java.util.ArrayList; + import java.util.List; + import java.util.Vector; + + import javax.swing.JCheckBoxMenuItem; + import javax.swing.JColorChooser; + import javax.swing.JInternalFrame; + import javax.swing.JMenu; + import javax.swing.JMenuItem; + import javax.swing.JOptionPane; + import javax.swing.event.InternalFrameAdapter; + import javax.swing.event.InternalFrameEvent; + import javax.swing.event.MenuEvent; + import javax.swing.event.MenuListener; + + /** + * GUI elements for handlnig an external chimera display + * + * @author jprocter + * + */ + public class ChimeraViewFrame extends GStructureViewer implements Runnable, + ViewSetProvider, JalviewStructureDisplayI + + { + private JalviewChimeraBindingModel jmb; + + /* + * list of sequenceSet ids associated with the view + */ + private ArrayList _aps = new ArrayList(); + + /* + * list of alignment panels to use for superposition + */ + private Vector _alignwith = new Vector(); + + /* + * list of alignment panels that are used for colouring structures by aligned + * sequences + */ + private Vector _colourwith = new Vector(); + + private boolean allChainsSelected = false; + + private boolean alignAddedStructures = false; + + AlignmentPanel ap; + + /* + * state flag for PDB retrieval thread + */ + private boolean _started = false; + + private boolean addingStructures = false; + + private IProgressIndicator progressBar = null; + + private String viewId = null; + + /* + * pdb retrieval thread. + */ + private Thread worker = null; + + /** + * Initialise menu options. + */ + private void initMenus() + { + viewerActionMenu.setText(MessageManager.getString("label.chimera")); + viewerColour.setText(MessageManager + .getString("label.colour_with_chimera")); + viewerColour.setToolTipText(MessageManager + .getString("label.let_chimera_manage_structure_colours")); + helpItem.setText(MessageManager.getString("label.chimera_help")); + seqColour.setSelected(jmb.isColourBySequence()); + viewerColour.setSelected(!jmb.isColourBySequence()); + if (_colourwith == null) + { + _colourwith = new Vector(); + } + if (_alignwith == null) + { + _alignwith = new Vector(); + } + + ViewSelectionMenu seqColourBy = new ViewSelectionMenu( + MessageManager.getString("label.colour_by"), this, _colourwith, + new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (!seqColour.isSelected()) + { + seqColour.doClick(); + } + else + { + // update the Chimera display now. + seqColour_actionPerformed(null); + } + } + }); + viewMenu.add(seqColourBy); + final ItemListener handler; + JMenu alpanels = new ViewSelectionMenu( + MessageManager.getString("label.superpose_with"), this, + _alignwith, handler = new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + alignStructs.setEnabled(_alignwith.size() > 0); + alignStructs.setToolTipText(MessageManager + .formatMessage( + "label.align_structures_using_linked_alignment_views", + new Object[] + { new Integer(_alignwith.size()).toString() })); + } + }); + handler.itemStateChanged(null); + viewerActionMenu.add(alpanels); + viewerActionMenu.addMenuListener(new MenuListener() + { + + @Override + public void menuSelected(MenuEvent e) + { + handler.itemStateChanged(null); + } + + @Override + public void menuDeselected(MenuEvent e) + { + // TODO Auto-generated method stub + } + + @Override + public void menuCanceled(MenuEvent e) + { + // TODO Auto-generated method stub + } + }); + } + + /** + * add a single PDB structure to a new or existing Chimera view + * + * @param pdbentry + * @param seq + * @param chains + * @param ap + */ + public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq, + String[] chains, final AlignmentPanel ap) + { + super(); + progressBar = ap.alignFrame; + // //////////////////////////////// + // Is the pdb file already loaded? + String alreadyMapped = ap.getStructureSelectionManager() + .alreadyMappedToFile(pdbentry.getId()); + + if (alreadyMapped != null) + { + int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop, + MessageManager.formatMessage( + "label.pdb_entry_is_already_displayed", new Object[] + { pdbentry.getId() }), MessageManager.formatMessage( + "label.map_sequences_to_visible_window", new Object[] + { pdbentry.getId() }), + JOptionPane.YES_NO_CANCEL_OPTION); + + if (option == JOptionPane.CANCEL_OPTION) + { + return; + } + if (option == JOptionPane.YES_OPTION) + { + // TODO : Fix multiple seq to one chain issue here. + ap.getStructureSelectionManager().setMapping(seq, chains, + alreadyMapped, AppletFormatAdapter.FILE); + if (ap.seqPanel.seqCanvas.fr != null) + { + ap.seqPanel.seqCanvas.fr.featuresAdded(); + ap.paintAlignment(true); + } + + // Now this ChimeraViewFrame is mapped to new sequences. We must add + // them to the existing array + JInternalFrame[] frames = Desktop.instance.getAllFrames(); + + for (JInternalFrame frame : frames) + { + if (frame instanceof ChimeraViewFrame) + { + final ChimeraViewFrame topView = ((ChimeraViewFrame) frame); + // JBPNOTE: this looks like a binding routine, rather than a gui + // routine + for (int pe = 0; pe < topView.jmb.pdbentry.length; pe++) + { + if (topView.jmb.pdbentry[pe].getFile().equals(alreadyMapped)) + { + topView.jmb.addSequence(pe, seq); + topView.addAlignmentPanel(ap); + // add it to the set used for colouring + topView.useAlignmentPanelForColourbyseq(ap); + topView.buildChimeraActionMenu(); + ap.getStructureSelectionManager() + .sequenceColoursChanged(ap); + break; + } + } + } + } + + return; + } + } + // ///////////////////////////////// + // Check if there are other Chimera views involving this alignment + // and prompt user about adding this molecule to one of them + List existingViews = getChimeraWindowsFor(ap); + for (ChimeraViewFrame topView : existingViews) + { + // TODO: highlight topView in view somehow + int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop, + MessageManager.formatMessage("label.add_pdbentry_to_view", + new Object[] + { pdbentry.getId(), topView.getTitle() }), + MessageManager + .getString("label.align_to_existing_structure_view"), + JOptionPane.YES_NO_CANCEL_OPTION); + if (option == JOptionPane.CANCEL_OPTION) + { + return; + } + if (option == JOptionPane.YES_OPTION) + { + topView.useAlignmentPanelForSuperposition(ap); + topView.addStructure(pdbentry, seq, chains, true, ap.alignFrame); + return; + } + } + // ///////////////////////////////// + openNewChimera(ap, new PDBEntry[] + { pdbentry }, new SequenceI[][] + { seq }); + } + + private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys, + SequenceI[][] seqs) + { + progressBar = ap.alignFrame; + jmb = new JalviewChimeraBindingModel(this, + ap.getStructureSelectionManager(), pdbentrys, seqs, null, null); + addAlignmentPanel(ap); + useAlignmentPanelForColourbyseq(ap); + if (pdbentrys.length > 1) + { + alignAddedStructures = true; + useAlignmentPanelForSuperposition(ap); + } + jmb.setColourBySequence(true); + setSize(400, 400); // probably should be a configurable/dynamic default here + initMenus(); + worker = null; + { + addingStructures = false; + worker = new Thread(this); + worker.start(); + } + this.addInternalFrameListener(new InternalFrameAdapter() + { + public void internalFrameClosing(InternalFrameEvent internalFrameEvent) + { + closeViewer(); + } + }); + + } + + /** + * create a new viewer containing several structures superimposed using the + * given alignPanel. + * + * @param ap + * @param pe + * @param seqs + */ + public ChimeraViewFrame(AlignmentPanel ap, PDBEntry[] pe, + SequenceI[][] seqs) + { + super(); + openNewChimera(ap, pe, seqs); + } + + public AlignmentPanel[] getAllAlignmentPanels() + { + AlignmentPanel[] t, list = new AlignmentPanel[0]; + for (String setid : _aps) + { + AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid); + if (panels != null) + { + t = new AlignmentPanel[list.length + panels.length]; + System.arraycopy(list, 0, t, 0, list.length); + System.arraycopy(panels, 0, t, list.length, panels.length); + list = t; + } + } + + return list; + } + + /** + * set the primary alignmentPanel reference and add another alignPanel to the + * list of ones to use for colouring and aligning + * + * @param nap + */ + public void addAlignmentPanel(AlignmentPanel nap) + { + if (ap == null) + { + ap = nap; + } + if (!_aps.contains(nap.av.getSequenceSetId())) + { + _aps.add(nap.av.getSequenceSetId()); + } + } + + /** + * remove any references held to the given alignment panel + * + * @param nap + */ + public void removeAlignmentPanel(AlignmentPanel nap) + { + try + { + _alignwith.remove(nap); + _colourwith.remove(nap); + if (ap == nap) + { + ap = null; + for (AlignmentPanel aps : getAllAlignmentPanels()) + { + if (aps != nap) + { + ap = aps; + break; + } + } + } + } catch (Exception ex) + { + } + if (ap != null) + { + buildChimeraActionMenu(); + } + } + + public void useAlignmentPanelForSuperposition(AlignmentPanel nap) + { + addAlignmentPanel(nap); + if (!_alignwith.contains(nap)) + { + _alignwith.add(nap); + } + } + + public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap) + { + if (_alignwith.contains(nap)) + { + _alignwith.remove(nap); + } + } + + public void useAlignmentPanelForColourbyseq(AlignmentPanel nap, + boolean enableColourBySeq) + { + useAlignmentPanelForColourbyseq(nap); + jmb.setColourBySequence(enableColourBySeq); + seqColour.setSelected(enableColourBySeq); + viewerColour.setSelected(!enableColourBySeq); + } + + public void useAlignmentPanelForColourbyseq(AlignmentPanel nap) + { + addAlignmentPanel(nap); + if (!_colourwith.contains(nap)) + { + _colourwith.add(nap); + } + } + + public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap) + { + if (_colourwith.contains(nap)) + { + _colourwith.remove(nap); + } + } + + /** + * add a new structure (with associated sequences and chains) to this viewer, + * retrieving it if necessary first. + * + * @param pdbentry + * @param seq + * @param chains + * @param alignFrame + * @param align + * if true, new structure(s) will be align using associated alignment + */ + private void addStructure(final PDBEntry pdbentry, final SequenceI[] seq, + final String[] chains, final boolean b, + final IProgressIndicator alignFrame) + { + if (pdbentry.getFile() == null) + { + if (worker != null && worker.isAlive()) + { + // a retrieval is in progress, wait around and add ourselves to the + // queue. + new Thread(new Runnable() + { + public void run() + { + while (worker != null && worker.isAlive() && _started) + { + try + { + Thread.sleep(100 + ((int) Math.random() * 100)); + + } catch (Exception e) + { + } + + } + // and call ourselves again. + addStructure(pdbentry, seq, chains, b, alignFrame); + } + }).start(); + return; + } + } + // otherwise, start adding the structure. + jmb.addSequenceAndChain(new PDBEntry[] + { pdbentry }, new SequenceI[][] + { seq }, new String[][] + { chains }); + addingStructures = true; + _started = false; + alignAddedStructures = b; + progressBar = alignFrame; // visual indication happens on caller frame. + (worker = new Thread(this)).start(); + return; + } + + private List getChimeraWindowsFor(AlignmentPanel apanel) + { + List result = new ArrayList(); + JInternalFrame[] frames = Desktop.instance.getAllFrames(); + + for (JInternalFrame frame : frames) + { + if (frame instanceof ChimeraViewFrame) + { + if (((ChimeraViewFrame) frame).isLinkedWith(apanel)) + { + result.add((ChimeraViewFrame) frame); + } + } + } + return result; + } + + void initChimera(String command) + { + jmb.setFinishedInit(false); + // TODO: consider waiting until the structure/view is fully loaded before + // displaying + jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(true), + getBounds().width, getBounds().height); + if (command == null) + { + command = ""; + } + jmb.evalStateCommand(command, false); + jmb.setFinishedInit(true); + } + + void setChainMenuItems(List chainNames) + { + chainMenu.removeAll(); + if (chainNames == null) + { + return; + } + JMenuItem menuItem = new JMenuItem( + MessageManager.getString("label.all")); + menuItem.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + allChainsSelected = true; + for (int i = 0; i < chainMenu.getItemCount(); i++) + { + if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem) + { + ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true); + } + } + centerViewer(); + allChainsSelected = false; + } + }); + + chainMenu.add(menuItem); + + for (String chainName : chainNames) + { + menuItem = new JCheckBoxMenuItem(chainName, true); + menuItem.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent evt) + { + if (!allChainsSelected) + { + centerViewer(); + } + } + }); + + chainMenu.add(menuItem); + } + } + + void centerViewer() + { + List toshow = new ArrayList(); + for (int i = 0; i < chainMenu.getItemCount(); i++) + { + if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem) + { + JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i); + if (item.isSelected()) + { + toshow.add(item.getText()); + } + } + } + jmb.centerViewer(toshow); + } + + /** + * Close down this instance of Jalview's Chimera viewer, giving the user the + * option to close the associated Chimera window (process). They may wish to + * keep it open until they have had an opportunity to save any work. + */ + public void closeViewer() + { + if (jmb.isChimeraRunning()) + { + String prompt = MessageManager + .formatMessage("label.confirm_close_chimera", new Object[] + { jmb.getViewerTitle(false) }); + prompt = JvSwingUtils.wrapTooltip(true, prompt); + int confirm = JOptionPane.showConfirmDialog(this, prompt, + MessageManager.getString("label.close_viewer"), + JOptionPane.YES_NO_OPTION); + jmb.closeViewer(confirm == JOptionPane.YES_OPTION); + } + ap = null; + _aps.clear(); + _alignwith.clear(); + _colourwith.clear(); + // TODO: check for memory leaks where instance isn't finalised because jmb + // holds a reference to the window + jmb = null; + } + + /** + * Open any newly added PDB structures in Chimera, having first fetched data + * from PDB (if not already saved). + */ + public void run() + { + _started = true; + // todo - record which pdbids were successfully imported. + StringBuilder errormsgs = new StringBuilder(128); + StringBuilder files = new StringBuilder(128); + List filePDB = new ArrayList(); + List filePDBpos = new ArrayList(); + PDBEntry thePdbEntry = null; + try + { + String[] curfiles = jmb.getPdbFile(); // files currently in viewer + // TODO: replace with reference fetching/transfer code (validate PDBentry + // as a DBRef?) + for (int pi = 0; pi < jmb.pdbentry.length; pi++) + { + String file = null; + thePdbEntry = jmb.pdbentry[pi]; + if (thePdbEntry.getFile() == null) + { + /* + * Retrieve PDB data, save to file, attach to PDBEntry + */ + file = fetchPdbFile(thePdbEntry); + if (file == null) + { + errormsgs.append("'" + thePdbEntry.getId() + "' "); + } + } + else + { + /* + * Got file already - ignore if already loaded in Chimera. + */ + file = new File(thePdbEntry.getFile()).getAbsoluteFile() + .getPath(); + if (curfiles != null && curfiles.length > 0) + { + addingStructures = true; // already files loaded. + for (int c = 0; c < curfiles.length; c++) + { + if (curfiles[c].equals(file)) + { + file = null; + break; + } + } + } + } + if (file != null) + { + filePDB.add(thePdbEntry); + filePDBpos.add(Integer.valueOf(pi)); + files.append(" \"" + Platform.escapeString(file) + "\""); + } + } + } catch (OutOfMemoryError oomerror) + { + new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(), + oomerror); + } catch (Exception ex) + { + ex.printStackTrace(); + errormsgs.append("When retrieving pdbfiles for '" + + thePdbEntry.getId() + "'"); + } + if (errormsgs.length() > 0) + { + + JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager + .formatMessage("label.pdb_entries_couldnt_be_retrieved", + new Object[] + { errormsgs.toString() }), MessageManager + .getString("label.couldnt_load_file"), + JOptionPane.ERROR_MESSAGE); + } + + if (files.length() > 0) + { + if (!addingStructures) + { + try + { + initChimera(""); + } catch (Exception ex) + { + Cache.log.error("Couldn't open Chimera viewer!", ex); + } + } + int num = -1; + for (PDBEntry pe : filePDB) + { + num++; + if (pe.getFile() != null) + { + try + { + int pos = filePDBpos.get(num).intValue(); + jmb.openFile(pe); + jmb.addSequence(pos, jmb.sequence[pos]); + File fl = new File(pe.getFile()); + String protocol = AppletFormatAdapter.URL; + try + { + if (fl.exists()) + { + protocol = AppletFormatAdapter.FILE; + } + } catch (Throwable e) + { + } + // Explicitly map to the filename used by Chimera ; + // TODO: use pe.getId() instead of pe.getFile() ? + jmb.ssm.setMapping(jmb.sequence[pos], null, pe.getFile(), + protocol); + } catch (OutOfMemoryError oomerror) + { + new OOMWarning( + "When trying to open and map structures from Chimera!", + oomerror); + } catch (Exception ex) + { + Cache.log.error("Couldn't open " + pe.getFile() + + " in Chimera viewer!", ex); + } finally + { + Cache.log.debug("File locations are " + files); + } + } + } + jmb.setFinishedInit(true); + jmb.setLoadingFromArchive(false); + + // refresh the sequence colours for the new structure(s) + for (AlignmentPanel ap : _colourwith) + { + jmb.updateColours(ap); + } + // do superposition if asked to + if (alignAddedStructures) + { + new Thread(new Runnable() + { + public void run() + { + alignStructs_withAllAlignPanels(); + } + }).start(); + alignAddedStructures = false; + } + addingStructures = false; + } + _started = false; + worker = null; + } + + /** + * Fetch PDB data and save to a local file. Returns the full path to the file, + * or null if fetch fails. + * + * @param processingEntry + * @return + * @throws Exception + */ + private String fetchPdbFile(PDBEntry processingEntry) throws Exception + { + String filePath = null; + Pdb pdbclient = new Pdb(); + AlignmentI pdbseq = null; + String pdbid = processingEntry.getId(); + long hdl = pdbid.hashCode() - System.currentTimeMillis(); + if (progressBar != null) + { + progressBar.setProgressBar(MessageManager.formatMessage( + "status.fetching_pdb", new Object[] + { pdbid }), hdl); + } + try + { + pdbseq = pdbclient.getSequenceRecords(pdbid); + } catch (OutOfMemoryError oomerror) + { + new OOMWarning("Retrieving PDB id " + pdbid, oomerror); + } finally + { + if (progressBar != null) + { + progressBar.setProgressBar( + MessageManager.getString("label.state_completed"), hdl); + } + } + /* + * If PDB data were saved and are not invalid (empty alignment), return the + * file path. + */ + if (pdbseq != null && pdbseq.getHeight() > 0) + { + // just use the file name from the first sequence's first PDBEntry + filePath = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId() + .elementAt(0)).getFile()).getAbsolutePath(); + processingEntry.setFile(filePath); + } + return filePath; + } + + @Override + public void pdbFile_actionPerformed(ActionEvent actionEvent) + { + JalviewFileChooser chooser = new JalviewFileChooser( + jalview.bin.Cache.getProperty("LAST_DIRECTORY")); + + chooser.setFileView(new JalviewFileView()); + chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file")); + chooser.setToolTipText(MessageManager.getString("action.save")); + + int value = chooser.showSaveDialog(this); + + if (value == JalviewFileChooser.APPROVE_OPTION) + { + BufferedReader in = null; + try + { + // TODO: cope with multiple PDB files in view + in = new BufferedReader(new FileReader(jmb.getPdbFile()[0])); + File outFile = chooser.getSelectedFile(); + + PrintWriter out = new PrintWriter(new FileOutputStream(outFile)); + String data; + while ((data = in.readLine()) != null) + { + if (!(data.indexOf("
") > -1 || data.indexOf("
") > -1)) + { + out.println(data); + } + } + out.close(); + } catch (Exception ex) + { + ex.printStackTrace(); + } finally + { + if (in != null) + { + try + { + in.close(); + } catch (IOException e) + { + e.printStackTrace(); + } + } + } + } + } + + @Override + public void viewMapping_actionPerformed(ActionEvent actionEvent) + { + jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer(); + try + { + for (int pdbe = 0; pdbe < jmb.pdbentry.length; pdbe++) + { + cap.appendText(jmb.printMapping(jmb.pdbentry[pdbe].getFile())); + cap.appendText("\n"); + } + } catch (OutOfMemoryError e) + { + new OOMWarning( + "composing sequence-structure alignments for display in text box.", + e); + cap.dispose(); + return; + } + jalview.gui.Desktop.addInternalFrame(cap, + MessageManager.getString("label.pdb_sequence_mapping"), 550, + 600); + } + + @Override + public void eps_actionPerformed(ActionEvent e) + { + throw new Error( + MessageManager + .getString("error.eps_generation_not_implemented")); + } + + @Override + public void png_actionPerformed(ActionEvent e) + { + throw new Error( + MessageManager + .getString("error.png_generation_not_implemented")); + } + + @Override + public void viewerColour_actionPerformed(ActionEvent actionEvent) + { + if (viewerColour.isSelected()) + { + // disable automatic sequence colouring. + jmb.setColourBySequence(false); + } + } + + @Override + public void seqColour_actionPerformed(ActionEvent actionEvent) + { + jmb.setColourBySequence(seqColour.isSelected()); + if (_colourwith == null) + { + _colourwith = new Vector(); + } + if (jmb.isColourBySequence()) + { + if (!jmb.isLoadingFromArchive()) + { + if (_colourwith.size() == 0 && ap != null) + { + // Make the currently displayed alignment panel the associated view + _colourwith.add(ap.alignFrame.alignPanel); + } + } + // Set the colour using the current view for the associated alignframe + for (AlignmentPanel ap : _colourwith) + { - jmb.colourBySequence(ap.av.showSequenceFeatures, ap); ++ jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap); + } + } + } + + @Override + public void chainColour_actionPerformed(ActionEvent actionEvent) + { + chainColour.setSelected(true); + jmb.colourByChain(); + } + + @Override + public void chargeColour_actionPerformed(ActionEvent actionEvent) + { + chargeColour.setSelected(true); + jmb.colourByCharge(); + } + + @Override + public void zappoColour_actionPerformed(ActionEvent actionEvent) + { + zappoColour.setSelected(true); + jmb.setJalviewColourScheme(new ZappoColourScheme()); + } + + @Override + public void taylorColour_actionPerformed(ActionEvent actionEvent) + { + taylorColour.setSelected(true); + jmb.setJalviewColourScheme(new TaylorColourScheme()); + } + + @Override + public void hydroColour_actionPerformed(ActionEvent actionEvent) + { + hydroColour.setSelected(true); + jmb.setJalviewColourScheme(new HydrophobicColourScheme()); + } + + @Override + public void helixColour_actionPerformed(ActionEvent actionEvent) + { + helixColour.setSelected(true); + jmb.setJalviewColourScheme(new HelixColourScheme()); + } + + @Override + public void strandColour_actionPerformed(ActionEvent actionEvent) + { + strandColour.setSelected(true); + jmb.setJalviewColourScheme(new StrandColourScheme()); + } + + @Override + public void turnColour_actionPerformed(ActionEvent actionEvent) + { + turnColour.setSelected(true); + jmb.setJalviewColourScheme(new TurnColourScheme()); + } + + @Override + public void buriedColour_actionPerformed(ActionEvent actionEvent) + { + buriedColour.setSelected(true); + jmb.setJalviewColourScheme(new BuriedColourScheme()); + } + + @Override + public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent) + { + setJalviewColourScheme(new PurinePyrimidineColourScheme()); + } + + @Override + public void userColour_actionPerformed(ActionEvent actionEvent) + { + userColour.setSelected(true); + new UserDefinedColours(this, null); + } + + @Override + public void backGround_actionPerformed(ActionEvent actionEvent) + { + java.awt.Color col = JColorChooser + .showDialog(this, MessageManager + .getString("label.select_backgroud_colour"), null); + if (col != null) + { + jmb.setBackgroundColour(col); + } + } + + @Override + public void showHelp_actionPerformed(ActionEvent actionEvent) + { + try + { + jalview.util.BrowserLauncher + .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide"); + } catch (Exception ex) + { + } + } + + public String getViewId() + { + if (viewId == null) + { + viewId = System.currentTimeMillis() + "." + this.hashCode(); + } + return viewId; + } + + public void updateTitleAndMenus() + { + if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0) + { + repaint(); + return; + } + setChainMenuItems(jmb.chainNames); + + this.setTitle(jmb.getViewerTitle(true)); + if (jmb.getPdbFile().length > 1 && jmb.sequence.length > 1) + { + viewerActionMenu.setVisible(true); + } + if (!jmb.isLoadingFromArchive()) + { + seqColour_actionPerformed(null); + } + } + + protected void buildChimeraActionMenu() + { + if (_alignwith == null) + { + _alignwith = new Vector(); + } + if (_alignwith.size() == 0 && ap != null) + { + _alignwith.add(ap); + } + ; + for (Component c : viewerActionMenu.getMenuComponents()) + { + if (c != alignStructs) + { + viewerActionMenu.remove((JMenuItem) c); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event + * .ActionEvent) + */ + @Override + protected void alignStructs_actionPerformed(ActionEvent actionEvent) + { + alignStructs_withAllAlignPanels(); + } + + private void alignStructs_withAllAlignPanels() + { + if (ap == null) + { + return; + } + ; + if (_alignwith.size() == 0) + { + _alignwith.add(ap); + } + ; + try + { + AlignmentI[] als = new Alignment[_alignwith.size()]; + ColumnSelection[] alc = new ColumnSelection[_alignwith.size()]; + int[] alm = new int[_alignwith.size()]; + int a = 0; + + for (AlignmentPanel ap : _alignwith) + { + als[a] = ap.av.getAlignment(); + alm[a] = -1; + alc[a++] = ap.av.getColumnSelection(); + } + jmb.superposeStructures(als, alm, alc); + } catch (Exception e) + { + StringBuffer sp = new StringBuffer(); + for (AlignmentPanel ap : _alignwith) + { + sp.append("'" + ap.alignFrame.getTitle() + "' "); + } + Cache.log.info("Couldn't align structures with the " + sp.toString() + + "associated alignment panels.", e); + + } + + } + + public void setJalviewColourScheme(ColourSchemeI ucs) + { + jmb.setJalviewColourScheme(ucs); + + } + + /** + * + * @param alignment + * @return first alignment panel displaying given alignment, or the default + * alignment panel + */ + public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment) + { + for (AlignmentPanel ap : getAllAlignmentPanels()) + { + if (ap.av.getAlignment() == alignment) + { + return ap; + } + } + return ap; + } + + /** + * + * @param ap2 + * @return true if this Chimera instance is linked with the given alignPanel + */ + public boolean isLinkedWith(AlignmentPanel ap2) + { + return _aps.contains(ap2.av.getSequenceSetId()); + } + + public boolean isUsedforaligment(AlignmentPanel ap2) + { + + return (_alignwith != null) && _alignwith.contains(ap2); + } + + public boolean isUsedforcolourby(AlignmentPanel ap2) + { + return (_colourwith != null) && _colourwith.contains(ap2); + } + + /** + * + * @return TRUE if the view is NOT being coloured by sequence associations. + */ + public boolean isColouredByChimera() + { + return !jmb.isColourBySequence(); + } + + public SequenceStructureBinding getBinding() + { + return jmb; + } + + } diff --cc src/jalview/gui/FeatureSettings.java index d30f5a0,693e6fe..66bd2fe --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@@ -20,29 -20,75 +20,71 @@@ */ package jalview.gui; - import java.io.*; - import java.util.*; - import java.util.List; - import java.awt.*; - import java.awt.event.*; - import java.beans.PropertyChangeEvent; - import java.beans.PropertyChangeListener; - - import javax.swing.*; - import javax.swing.event.*; - import javax.swing.table.*; - --import jalview.analysis.AlignmentSorter; - import jalview.api.FeaturesDisplayedI; import jalview.bin.Cache; --import jalview.commands.OrderCommand; - import jalview.datamodel.*; - import jalview.io.*; -import jalview.datamodel.AlignmentI; + import jalview.datamodel.SequenceFeature; -import jalview.datamodel.SequenceGroup; + import jalview.datamodel.SequenceI; + import jalview.io.JalviewFileChooser; import jalview.schemes.AnnotationColourGradient; import jalview.schemes.GraduatedColor; import jalview.util.MessageManager; import jalview.ws.dbsources.das.api.jalviewSourceI; + import java.awt.BorderLayout; + import java.awt.Color; + import java.awt.Component; + import java.awt.Font; + import java.awt.Graphics; + import java.awt.GridLayout; + import java.awt.Rectangle; + import java.awt.event.ActionEvent; + import java.awt.event.ActionListener; + import java.awt.event.ItemEvent; + import java.awt.event.ItemListener; + import java.awt.event.MouseAdapter; + import java.awt.event.MouseEvent; + import java.awt.event.MouseMotionAdapter; + import java.beans.PropertyChangeEvent; + import java.beans.PropertyChangeListener; + import java.io.File; + import java.io.FileInputStream; + import java.io.FileOutputStream; + import java.io.InputStreamReader; + import java.io.OutputStreamWriter; + import java.io.PrintWriter; -import java.util.ArrayList; + import java.util.Hashtable; + import java.util.Iterator; + import java.util.List; ++import java.util.Set; + import java.util.Vector; + + import javax.swing.AbstractCellEditor; + import javax.swing.BorderFactory; + import javax.swing.Icon; + import javax.swing.JButton; + import javax.swing.JCheckBox; + import javax.swing.JCheckBoxMenuItem; + import javax.swing.JColorChooser; + import javax.swing.JDialog; + import javax.swing.JInternalFrame; + import javax.swing.JLabel; + import javax.swing.JLayeredPane; + import javax.swing.JMenuItem; + import javax.swing.JOptionPane; + import javax.swing.JPanel; + import javax.swing.JPopupMenu; + import javax.swing.JScrollPane; + import javax.swing.JSlider; + import javax.swing.JTabbedPane; + import javax.swing.JTable; + import javax.swing.ListSelectionModel; + import javax.swing.SwingConstants; + import javax.swing.SwingUtilities; + import javax.swing.event.ChangeEvent; + import javax.swing.event.ChangeListener; + import javax.swing.table.AbstractTableModel; + import javax.swing.table.TableCellEditor; + import javax.swing.table.TableCellRenderer; + public class FeatureSettings extends JPanel { DasSourceBrowser dassourceBrowser; @@@ -104,11 -148,11 +146,11 @@@ public void mousePressed(MouseEvent evt) { selectedRow = table.rowAtPoint(evt.getPoint()); - if (evt.isPopupTrigger()) + if (SwingUtilities.isRightMouseButton(evt)) { popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.minmax, evt.getX(), - evt.getY()); + table.getValueAt(selectedRow, 1), fr.getMinMax(), + evt.getX(), evt.getY()); } else if (evt.getClickCount() == 2) { @@@ -118,6 -162,19 +160,20 @@@ (String) table.getValueAt(selectedRow, 0)); } } + + // isPopupTrigger fires on mouseReleased on Mac + @Override + public void mouseReleased(MouseEvent evt) + { + selectedRow = table.rowAtPoint(evt.getPoint()); + if (evt.isPopupTrigger()) + { + popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.minmax, evt.getX(), ++ table.getValueAt(selectedRow, 1), fr.getMinMax(), ++ evt.getX(), + evt.getY()); + } + } }); table.addMouseMotionListener(new MouseMotionAdapter() @@@ -618,12 -691,12 +672,18 @@@ { order[i] = fr.getOrder(data[i][0].toString()); if (order[i] < 0) ++ { order[i] = fr.setOrder(data[i][0].toString(), i / order.length); ++ } if (i > 1) ++ { sort = sort || order[i - 1] > order[i]; ++ } } if (sort) ++ { jalview.util.QuickSort.sort(order, data); ++ } } void load() @@@ -815,7 -887,7 +875,9 @@@ public void orderByAvWidth() { if (table == null || table.getModel() == null) ++ { return; ++ } Object[][] data = ((FeatureTableModel) table.getModel()).getData(); float[] width = new float[data.length]; float[] awidth; @@@ -837,7 -909,7 +899,9 @@@ width[i] = 0; } if (max < width[i]) ++ { max = width[i]; ++ } } boolean sort = false; for (int i = 0; i < width.length; i++) @@@ -857,11 -929,11 +921,15 @@@ fr.setOrder(data[i][0].toString(), width[i]); // store for later } if (i > 0) ++ { sort = sort || width[i - 1] > width[i]; ++ } } if (sort) ++ { jalview.util.QuickSort.sort(width, data); // update global priority order ++ } updateFeatureRenderer(data, false); table.repaint(); diff --cc src/jalview/gui/Jalview2XML.java index d7453c8,b608b95..2ceb245 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@@ -1359,7 -1419,16 +1423,17 @@@ public class Jalview2XM calcIdSet.add(aa[i].getCalcId()); an.setCalcId(aa[i].getCalcId()); } + if (aa[i].hasProperties()) + { + for (String pr : aa[i].getProperties()) + { + Property prop = new Property(); + prop.setName(pr); + prop.setValue(aa[i].getProperty(pr)); + an.addProperty(prop); + } + } + AnnotationElement ae; if (aa[i].annotations != null) { @@@ -4429,5 -4525,5 +4534,4 @@@ { skipList = skipList2; } -- } diff --cc src/jalview/gui/Jalview2XML_V1.java index f246fd3,9263cd9..f1879c1 --- a/src/jalview/gui/Jalview2XML_V1.java +++ b/src/jalview/gui/Jalview2XML_V1.java @@@ -20,19 -20,36 +20,39 @@@ */ package jalview.gui; - import java.io.*; - import java.util.*; - import java.util.jar.*; + import jalview.binding.Annotation; + import jalview.binding.AnnotationElement; + import jalview.binding.Features; + import jalview.binding.JGroup; + import jalview.binding.JSeq; + import jalview.binding.JalviewModel; + import jalview.binding.JalviewModelSequence; + import jalview.binding.Pdbids; + import jalview.binding.Sequence; + import jalview.binding.SequenceSet; + import jalview.binding.Setting; + import jalview.binding.Tree; + import jalview.binding.UserColours; + import jalview.binding.Viewport; + import jalview.schemes.ColourSchemeI; + import jalview.schemes.ColourSchemeProperty; + import jalview.schemes.ResidueProperties; + import jalview.structure.StructureSelectionManager; + import jalview.util.MessageManager; + import jalview.util.jarInputStreamProvider; - import javax.swing.*; + import java.io.InputStreamReader; + import java.util.Hashtable; + import java.util.Vector; + import java.util.jar.JarEntry; + import java.util.jar.JarInputStream; - import org.exolab.castor.xml.*; + import javax.swing.JOptionPane; - import jalview.binding.*; - import jalview.schemes.*; +import jalview.util.MessageManager; +import jalview.util.jarInputStreamProvider; + import org.exolab.castor.xml.IDResolver; +import jalview.viewmodel.seqfeatures.FeatureRendererSettings; /** * DOCUMENT ME! diff --cc src/jalview/gui/JalviewChimeraBindingModel.java index 0000000,c582c5d..d22c46c mode 000000,100644..100644 --- a/src/jalview/gui/JalviewChimeraBindingModel.java +++ b/src/jalview/gui/JalviewChimeraBindingModel.java @@@ -1,0 -1,104 +1,104 @@@ + package jalview.gui; + + import jalview.api.AlignmentViewPanel; + import jalview.datamodel.PDBEntry; + import jalview.datamodel.SequenceI; + import jalview.ext.rbvi.chimera.JalviewChimeraBinding; + import jalview.structure.StructureSelectionManager; + + public class JalviewChimeraBindingModel extends JalviewChimeraBinding + { + private ChimeraViewFrame cvf; + + public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame, + StructureSelectionManager ssm, PDBEntry[] pdbentry, + SequenceI[][] sequenceIs, String[][] chains, String protocol) + { + super(ssm, pdbentry, sequenceIs, chains, protocol); + cvf = chimeraViewFrame; + } + + FeatureRenderer fr = null; + + @Override + public jalview.api.FeatureRenderer getFeatureRenderer( + AlignmentViewPanel alignment) + { + AlignmentPanel ap = (alignment == null) ? cvf.ap + : (AlignmentPanel) alignment; - if (ap.av.showSequenceFeatures) ++ if (ap.av.isShowSequenceFeatures()) + { + if (fr == null) + { - fr = ap.cloneFeatureRenderer(); ++ fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer(); + } + else + { + ap.updateFeatureRenderer(fr); + } + } + + return fr; + } + + @Override + public jalview.api.SequenceRenderer getSequenceRenderer( + AlignmentViewPanel alignment) + { + return new SequenceRenderer(((AlignmentPanel) alignment).av); + } + @Override + public void refreshGUI() + { + // appJmolWindow.repaint(); + javax.swing.SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + cvf.updateTitleAndMenus(); + cvf.revalidate(); + } + }); + } + + public void updateColours(Object source) + { + AlignmentPanel ap = (AlignmentPanel) source, topap; + // ignore events from panels not used to colour this view + if (!cvf.isUsedforcolourby(ap)) + { + return; + } + if (!isLoadingFromArchive()) + { - colourBySequence(ap.av.getShowSequenceFeatures(), ap); ++ colourBySequence(ap.av.isShowSequenceFeatures(), ap); + } + } + @Override + public void releaseReferences(Object svl) + { + // TODO Auto-generated method stub + + } + + @Override + protected void releaseUIResources() + { + // TODO Auto-generated method stub + + } + + @Override + public void refreshPdbEntries() + { + // TODO Auto-generated method stub + + } + + @Override + public void showUrl(String url, String target) + { + // TODO Auto-generated method stub + + } + } diff --cc src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index c3b6c1e,0000000..30d14c2 mode 100644,000000..100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@@ -1,942 -1,0 +1,942 @@@ +package jalview.viewmodel.seqfeatures; + +import jalview.api.AlignViewportI; +import jalview.api.FeaturesDisplayedI; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureRenderer; +import jalview.schemes.GraduatedColor; +import jalview.viewmodel.AlignmentViewport; + +import java.awt.Color; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.ArrayList; +import java.util.Arrays; - import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; - import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class FeatureRendererModel implements + jalview.api.FeatureRenderer +{ + + /** + * global transparency for feature + */ + protected float transparency = 1.0f; + + protected Map featureColours = new ConcurrentHashMap(); + + protected Map featureGroups = new ConcurrentHashMap(); + + protected Object currentColour; + + protected String[] renderOrder; + + protected PropertyChangeSupport changeSupport = new PropertyChangeSupport( + this); + + protected AlignmentViewport av; + + public AlignViewportI getViewport() + { + return av; + } + + public FeatureRendererSettings getSettings() + { + return new FeatureRendererSettings(this); + } + + public void transferSettings(FeatureRendererSettings fr) + { + this.renderOrder = fr.renderOrder; + this.featureGroups = fr.featureGroups; + this.featureColours = fr.featureColours; + this.transparency = fr.transparency; + this.featureOrder = fr.featureOrder; + } + + /** + * update from another feature renderer + * + * @param fr + * settings to copy + */ + public void transferSettings(jalview.api.FeatureRenderer _fr) + { + FeatureRenderer fr = (FeatureRenderer) _fr; + FeatureRendererSettings frs = new FeatureRendererSettings(fr); + this.renderOrder = frs.renderOrder; + this.featureGroups = frs.featureGroups; + this.featureColours = frs.featureColours; + this.transparency = frs.transparency; + this.featureOrder = frs.featureOrder; + if (av != null && av != fr.getViewport()) + { + // copy over the displayed feature settings + if (_fr.getFeaturesDisplayed() != null) + { + FeaturesDisplayedI fd = getFeaturesDisplayed(); + if (fd == null) + { + setFeaturesDisplayedFrom(_fr.getFeaturesDisplayed()); + } + else + { + synchronized (fd) + { + fd.clear(); + java.util.Iterator fdisp = _fr.getFeaturesDisplayed() + .getVisibleFeatures(); + while (fdisp.hasNext()) + { + fd.setVisible(fdisp.next()); + } + } + } + } + } + } + + public void setFeaturesDisplayedFrom(FeaturesDisplayedI featuresDisplayed) + { + av.setFeaturesDisplayed(new FeaturesDisplayed(featuresDisplayed)); + } + + @Override + public void setVisible(String featureType) + { + FeaturesDisplayedI fdi = av.getFeaturesDisplayed(); + if (fdi == null) + { + av.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); + } + if (!fdi.isRegistered(featureType)) + { + pushFeatureType(Arrays.asList(new String[] + { featureType })); + } + fdi.setVisible(featureType); + } + + @Override + public void setAllVisible(List featureTypes) + { + FeaturesDisplayedI fdi = av.getFeaturesDisplayed(); + if (fdi == null) + { + av.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); + } + List nft = new ArrayList(); + for (String featureType : featureTypes) + { + if (!fdi.isRegistered(featureType)) + { + nft.add(featureType); + } + } + if (nft.size() > 0) + { + pushFeatureType(nft); + } + fdi.setAllVisible(featureTypes); + } + + /** + * push a set of new types onto the render order stack. Note - this is a + * direct mechanism rather than the one employed in updateRenderOrder + * + * @param types + */ + private void pushFeatureType(List types) + { + + int ts = types.size(); + String neworder[] = new String[(renderOrder == null ? 0 + : renderOrder.length) + ts]; + types.toArray(neworder); + if (renderOrder != null) + { + System.arraycopy(neworder,0,neworder,renderOrder.length,ts); + System.arraycopy(renderOrder, 0, neworder, 0, renderOrder.length); + } + renderOrder = neworder; + } + + protected Hashtable minmax = new Hashtable(); + + public Hashtable getMinMax() + { + return minmax; + } + + /** + * normalise a score against the max/min bounds for the feature type. + * + * @param sequenceFeature + * @return byte[] { signed, normalised signed (-127 to 127) or unsigned + * (0-255) value. + */ + protected final byte[] normaliseScore(SequenceFeature sequenceFeature) + { + float[] mm = ((float[][]) minmax.get(sequenceFeature.type))[0]; + final byte[] r = new byte[] + { 0, (byte) 255 }; + if (mm != null) + { + if (r[0] != 0 || mm[0] < 0.0) + { + r[0] = 1; + r[1] = (byte) ((int) 128.0 + 127.0 * (sequenceFeature.score / mm[1])); + } + else + { + r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1])); + } + } + return r; + } + + boolean newFeatureAdded = false; + + boolean findingFeatures = false; + + protected boolean updateFeatures() + { + if (av.getFeaturesDisplayed() == null || renderOrder == null + || newFeatureAdded) + { + findAllFeatures(); + if (av.getFeaturesDisplayed().getVisibleFeatureCount() < 1) + { + return false; + } + } + // TODO: decide if we should check for the visible feature count first + return true; + } + + /** + * search the alignment for all new features, give them a colour and display + * them. Then fires a PropertyChangeEvent on the changeSupport object. + * + */ + protected void findAllFeatures() + { + synchronized (firing) + { + if (firing.equals(Boolean.FALSE)) + { + firing = Boolean.TRUE; + findAllFeatures(true); // add all new features as visible + changeSupport.firePropertyChange("changeSupport", null, null); + firing = Boolean.FALSE; + } + } + } + + @Override + public List findFeaturesAtRes(SequenceI sequence, int res) + { + ArrayList tmp = new ArrayList(); + SequenceFeature[] features = sequence.getSequenceFeatures(); + if (features != null) + { + for (int i = 0; i < features.length; i++) + { + if (!av.areFeaturesDisplayed() + || !av.getFeaturesDisplayed().isVisible( + features[i].getType())) + { + continue; + } + + if (features[i].featureGroup != null + && featureGroups != null + && featureGroups.containsKey(features[i].featureGroup) - && !((Boolean) featureGroups.get(features[i].featureGroup)) ++ && !featureGroups.get(features[i].featureGroup) + .booleanValue()) ++ { + continue; ++ } + + if ((features[i].getBegin() <= res) + && (features[i].getEnd() >= res)) + { + tmp.add(features[i]); + } + } + } + return tmp; + } + + /** + * Searches alignment for all features and updates colours + * + * @param newMadeVisible + * if true newly added feature types will be rendered immediatly + * TODO: check to see if this method should actually be proxied so + * repaint events can be propagated by the renderer code + */ + @Override + public synchronized void findAllFeatures(boolean newMadeVisible) + { + newFeatureAdded = false; + + if (findingFeatures) + { + newFeatureAdded = true; + return; + } + + findingFeatures = true; + if (av.getFeaturesDisplayed() == null) + { + av.setFeaturesDisplayed(new FeaturesDisplayed()); + } + FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed(); + + ArrayList allfeatures = new ArrayList(); + ArrayList oldfeatures = new ArrayList(); + if (renderOrder != null) + { + for (int i = 0; i < renderOrder.length; i++) + { + if (renderOrder[i] != null) + { + oldfeatures.add(renderOrder[i]); + } + } + } + if (minmax == null) + { + minmax = new Hashtable(); + } + AlignmentI alignment = av.getAlignment(); + for (int i = 0; i < alignment.getHeight(); i++) + { + SequenceI asq = alignment.getSequenceAt(i); + SequenceI dasq = asq.getDatasetSequence(); + SequenceFeature[] features = dasq != null ? dasq + .getSequenceFeatures() : asq.getSequenceFeatures(); + + if (features == null) + { + continue; + } + + int index = 0; + while (index < features.length) + { + if (!featuresDisplayed.isRegistered(features[index].getType())) + { + String fgrp = features[index].getFeatureGroup(); + if (fgrp != null) + { + Boolean groupDisplayed = featureGroups.get(fgrp); + if (groupDisplayed == null) + { + groupDisplayed = Boolean.valueOf(newMadeVisible); + featureGroups.put(fgrp, groupDisplayed); + } - if (!((Boolean) groupDisplayed).booleanValue()) ++ if (!groupDisplayed.booleanValue()) + { + index++; + continue; + } + } + if (!(features[index].begin == 0 && features[index].end == 0)) + { + // If beginning and end are 0, the feature is for the whole sequence + // and we don't want to render the feature in the normal way + + if (newMadeVisible + && !oldfeatures.contains(features[index].getType())) + { + // this is a new feature type on the alignment. Mark it for + // display. + featuresDisplayed.setVisible(features[index].getType()); + setOrder(features[index].getType(), 0); + } + } + } + if (!allfeatures.contains(features[index].getType())) + { + allfeatures.add(features[index].getType()); + } + if (features[index].score != Float.NaN) + { + int nonpos = features[index].getBegin() >= 1 ? 0 : 1; + float[][] mm = (float[][]) minmax.get(features[index].getType()); + if (mm == null) + { + mm = new float[][] + { null, null }; + minmax.put(features[index].getType(), mm); + } + if (mm[nonpos] == null) + { + mm[nonpos] = new float[] + { features[index].score, features[index].score }; + + } + else + { + if (mm[nonpos][0] > features[index].score) + { + mm[nonpos][0] = features[index].score; + } + if (mm[nonpos][1] < features[index].score) + { + mm[nonpos][1] = features[index].score; + } + } + } + index++; + } + } + updateRenderOrder(allfeatures); + findingFeatures = false; + } + + protected Boolean firing = Boolean.FALSE; + + /** + * replaces the current renderOrder with the unordered features in + * allfeatures. The ordering of any types in both renderOrder and allfeatures + * is preserved, and all new feature types are rendered on top of the existing + * types, in the order given by getOrder or the order given in allFeatures. + * Note. this operates directly on the featureOrder hash for efficiency. TODO: + * eliminate the float storage for computing/recalling the persistent ordering + * New Cability: updates min/max for colourscheme range if its dynamic + * + * @param allFeatures + */ + private void updateRenderOrder(List allFeatures) + { + List allfeatures = new ArrayList(allFeatures); + String[] oldRender = renderOrder; + renderOrder = new String[allfeatures.size()]; + Object mmrange, fc = null; + boolean initOrders = (featureOrder == null); + int opos = 0; + if (oldRender != null && oldRender.length > 0) + { + for (int j = 0; j < oldRender.length; j++) + { + if (oldRender[j] != null) + { + if (initOrders) + { + setOrder(oldRender[j], (1 - (1 + (float) j) - / (float) oldRender.length)); ++ / oldRender.length)); + } + if (allfeatures.contains(oldRender[j])) + { + renderOrder[opos++] = oldRender[j]; // existing features always + // appear below new features + allfeatures.remove(oldRender[j]); + if (minmax != null) + { + mmrange = minmax.get(oldRender[j]); + if (mmrange != null) + { + fc = featureColours.get(oldRender[j]); + if (fc != null && fc instanceof GraduatedColor + && ((GraduatedColor) fc).isAutoScale()) + { + ((GraduatedColor) fc).updateBounds( + ((float[][]) mmrange)[0][0], + ((float[][]) mmrange)[0][1]); + } + } + } + } + } + } + } + if (allfeatures.size() == 0) + { + // no new features - leave order unchanged. + return; + } + int i = allfeatures.size() - 1; + int iSize = i; + boolean sort = false; + String[] newf = new String[allfeatures.size()]; + float[] sortOrder = new float[allfeatures.size()]; + for (String newfeat : allfeatures) + { + newf[i] = newfeat; + if (minmax != null) + { + // update from new features minmax if necessary + mmrange = minmax.get(newf[i]); + if (mmrange != null) + { + fc = featureColours.get(newf[i]); + if (fc != null && fc instanceof GraduatedColor + && ((GraduatedColor) fc).isAutoScale()) + { + ((GraduatedColor) fc).updateBounds(((float[][]) mmrange)[0][0], + ((float[][]) mmrange)[0][1]); + } + } + } + if (initOrders || !featureOrder.containsKey(newf[i])) + { + int denom = initOrders ? allfeatures.size() : featureOrder.size(); + // new unordered feature - compute persistent ordering at head of + // existing features. + setOrder(newf[i], i / (float) denom); + } + // set order from newly found feature from persisted ordering. + sortOrder[i] = 2 - ((Float) featureOrder.get(newf[i])).floatValue(); + if (i < iSize) + { + // only sort if we need to + sort = sort || sortOrder[i] > sortOrder[i + 1]; + } + i--; + } + if (iSize > 1 && sort) + { + jalview.util.QuickSort.sort(sortOrder, newf); + } + sortOrder = null; + System.arraycopy(newf, 0, renderOrder, opos, newf.length); + } + + /** + * get a feature style object for the given type string. Creates a + * java.awt.Color for a featureType with no existing colourscheme. TODO: + * replace return type with object implementing standard abstract colour/style + * interface + * + * @param featureType + * @return java.awt.Color or GraduatedColor + */ + public Object getFeatureStyle(String featureType) + { + Object fc = featureColours.get(featureType); + if (fc == null) + { + jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(); + Color col = ucs.createColourFromName(featureType); + featureColours.put(featureType, fc = col); + } + return fc; + } + + /** + * return a nominal colour for this feature + * + * @param featureType + * @return standard color, or maximum colour for graduated colourscheme + */ + public Color getColour(String featureType) + { + Object fc = getFeatureStyle(featureType); + + if (fc instanceof Color) + { + return (Color) fc; + } + else + { + if (fc instanceof GraduatedColor) + { + return ((GraduatedColor) fc).getMaxColor(); + } + } + throw new Error("Implementation Error: Unrecognised render object " + + fc.getClass() + " for features of type " + featureType); + } + + /** + * calculate the render colour for a specific feature using current feature + * settings. + * + * @param feature + * @return render colour for the given feature + */ + public Color getColour(SequenceFeature feature) + { + Object fc = getFeatureStyle(feature.getType()); + if (fc instanceof Color) + { + return (Color) fc; + } + else + { + if (fc instanceof GraduatedColor) + { + return ((GraduatedColor) fc).findColor(feature); + } + } + throw new Error("Implementation Error: Unrecognised render object " + + fc.getClass() + " for features of type " + feature.getType()); + } + + protected boolean showFeature(SequenceFeature sequenceFeature) + { + Object fc = getFeatureStyle(sequenceFeature.type); + if (fc instanceof GraduatedColor) + { + return ((GraduatedColor) fc).isColored(sequenceFeature); + } + else + { + return true; + } + } + + protected boolean showFeatureOfType(String type) + { + return av.getFeaturesDisplayed().isVisible(type); + } + + public void setColour(String featureType, Object col) + { + // overwrite + // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof + // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null; + // Object c = featureColours.get(featureType); + // if (c == null || c instanceof Color || (c instanceof GraduatedColor && + // !((GraduatedColor)c).getMaxColor().equals(_col))) + { + featureColours.put(featureType, col); + } + } + + public void setTransparency(float value) + { + transparency = value; + } + + public float getTransparency() + { + return transparency; + } + + Map featureOrder = null; + + /** + * analogous to colour - store a normalized ordering for all feature types in + * this rendering context. + * + * @param type + * Feature type string + * @param position + * normalized priority - 0 means always appears on top, 1 means + * always last. + */ + public float setOrder(String type, float position) + { + if (featureOrder == null) + { + featureOrder = new Hashtable(); + } + featureOrder.put(type, new Float(position)); + return position; + } + + /** + * get the global priority (0 (top) to 1 (bottom)) + * + * @param type + * @return [0,1] or -1 for a type without a priority + */ + public float getOrder(String type) + { + if (featureOrder != null) + { + if (featureOrder.containsKey(type)) + { + return ((Float) featureOrder.get(type)).floatValue(); + } + } + return -1; + } + + @Override - public Map getFeatureColours() ++ public Map getFeatureColours() + { - return new ConcurrentHashMap<>(featureColours); ++ return new ConcurrentHashMap(featureColours); + } + + /** + * Replace current ordering with new ordering + * + * @param data + * { String(Type), Colour(Type), Boolean(Displayed) } + */ + public void setFeaturePriority(Object[][] data) + { + setFeaturePriority(data, true); + } + + /** + * + * @param data + * { String(Type), Colour(Type), Boolean(Displayed) } + * @param visibleNew + * when true current featureDisplay list will be cleared + */ + public void setFeaturePriority(Object[][] data, boolean visibleNew) + { + FeaturesDisplayedI av_featuresdisplayed = null; + if (visibleNew) + { + if ((av_featuresdisplayed = av.getFeaturesDisplayed()) != null) + { + av.getFeaturesDisplayed().clear(); + } + else + { + av.setFeaturesDisplayed(av_featuresdisplayed = new FeaturesDisplayed()); + } + } + else + { + av_featuresdisplayed = av.getFeaturesDisplayed(); + } + if (data == null) + { + return; + } + // The feature table will display high priority + // features at the top, but theses are the ones + // we need to render last, so invert the data + renderOrder = new String[data.length]; + + if (data.length > 0) + { + for (int i = 0; i < data.length; i++) + { + String type = data[i][0].toString(); + setColour(type, data[i][1]); // todo : typesafety - feature color + // interface object + if (((Boolean) data[i][2]).booleanValue()) + { + av_featuresdisplayed.setVisible(type); + } + + renderOrder[data.length - i - 1] = type; + } + } + + } + + /** + * @param listener + * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener) + */ + public void addPropertyChangeListener(PropertyChangeListener listener) + { + changeSupport.addPropertyChangeListener(listener); + } + + /** + * @param listener + * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener) + */ + public void removePropertyChangeListener(PropertyChangeListener listener) + { + changeSupport.removePropertyChangeListener(listener); + } + + public Set getAllFeatureColours() + { + return featureColours.keySet(); + } + + public void clearRenderOrder() + { + renderOrder = null; + } + + public boolean hasRenderOrder() + { + return renderOrder != null; + } + + public List getRenderOrder() + { + if (renderOrder == null) + { + return Arrays.asList(new String[] + {}); + } + return Arrays.asList(renderOrder); + } + + public int getFeatureGroupsSize() + { + return featureGroups != null ? 0 : featureGroups.size(); + } + + @Override + public List getFeatureGroups() + { + // conflict between applet and desktop - featureGroups returns the map in + // the desktop featureRenderer + return (featureGroups == null) ? Arrays.asList(new String[0]) : Arrays + .asList(featureGroups.keySet().toArray(new String[0])); + } + + public boolean checkGroupVisibility(String group, boolean newGroupsVisible) + { + if (featureGroups == null) + { + // then an exception happens next.. + } + if (featureGroups.containsKey(group)) + { - return ((Boolean) featureGroups.get(group)).booleanValue(); ++ return featureGroups.get(group).booleanValue(); + } + if (newGroupsVisible) + { + featureGroups.put(group, new Boolean(true)); + return true; + } + return false; + } + + /** + * get visible or invisible groups + * + * @param visible + * true to return visible groups, false to return hidden ones. + * @return list of groups + */ + @Override + public List getGroups(boolean visible) + { + if (featureGroups != null) + { + ArrayList gp = new ArrayList(); + + for (Object grp : featureGroups.keySet()) + { - Boolean state = (Boolean) featureGroups.get(grp); ++ Boolean state = featureGroups.get(grp); + if (state.booleanValue() == visible) + { + gp.add(grp); + } + } + return gp; + } + return null; + } + + @Override + public void setGroupVisibility(String group, boolean visible) + { + featureGroups.put(group, new Boolean(visible)); + } + + @Override + public void setGroupVisibility(List toset, boolean visible) + { + if (toset != null && toset.size() > 0 && featureGroups != null) + { + boolean rdrw = false; + for (String gst : toset) + { + Boolean st = featureGroups.get(gst); + featureGroups.put(gst, new Boolean(visible)); + if (st != null) + { - rdrw = rdrw || (visible != ((Boolean) st).booleanValue()); ++ rdrw = rdrw || (visible != st.booleanValue()); + } + } + if (rdrw) + { + // set local flag indicating redraw needed ? + } + } + } + + @Override + public Hashtable getDisplayedFeatureCols() + { + Hashtable fcols = new Hashtable(); + if (getViewport().getFeaturesDisplayed() == null) + { + return fcols; + } + Iterator en = getViewport().getFeaturesDisplayed() + .getVisibleFeatures(); + while (en.hasNext()) + { + String col = en.next(); + fcols.put(col, getColour(col)); + } + return fcols; + } + + @Override + public FeaturesDisplayedI getFeaturesDisplayed() + { + return av.getFeaturesDisplayed(); + } + + @Override + public String[] getDisplayedFeatureTypes() + { + String[] typ = null; + typ = getRenderOrder().toArray(new String[0]); + FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed(); + if (feature_disp != null) + { + synchronized (feature_disp) + { + for (int i = 0; i < typ.length; i++) + { + if (feature_disp.isVisible(typ[i])) + { + typ[i] = null; + } + } + } + } + return typ; + } + + @Override + public String[] getDisplayedFeatureGroups() + { + String[] gps = null; + ArrayList _gps = new ArrayList(); + Iterator en = getFeatureGroups().iterator(); + int g = 0; + boolean valid = false; + while (en.hasNext()) + { + String gp = (String) en.next(); + if (checkGroupVisibility(gp, false)) + { + valid = true; + _gps.add(gp); + } + if (!valid) + { + return null; + } + else + { + gps = new String[_gps.size()]; + _gps.toArray(gps); + } + } + return gps; + } + +} diff --cc src/jalview/ws/AWSThread.java index 1fa4663,edb56a9..d3682d4 --- a/src/jalview/ws/AWSThread.java +++ b/src/jalview/ws/AWSThread.java @@@ -28,7 -28,8 +28,8 @@@ import jalview.datamodel.AlignmentView import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.WebserviceInfo; -import jalview.gui.FeatureRenderer.FeatureRendererSettings; +import jalview.viewmodel.seqfeatures.FeatureRendererSettings; + import jalview.util.MessageManager; public abstract class AWSThread extends Thread {