/* * Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fhcrc.cpl.viewer.gui; import org.fhcrc.cpl.toolbox.proteomics.gui.IntensityPlot; import org.fhcrc.cpl.viewer.util.SharedProperties; import org.fhcrc.cpl.toolbox.proteomics.feature.Spectrum; import org.fhcrc.cpl.toolbox.proteomics.feature.FeatureSet; import org.fhcrc.cpl.toolbox.proteomics.feature.Feature; import org.fhcrc.cpl.toolbox.proteomics.feature.extraInfo.IsotopicLabelExtraInfoDef; import org.fhcrc.cpl.toolbox.proteomics.feature.extraInfo.FeatureExtraInformationDef; import org.fhcrc.cpl.toolbox.proteomics.MSRun; import org.fhcrc.cpl.viewer.Application; import org.fhcrc.cpl.viewer.gui.WorkbenchFrame; import org.fhcrc.cpl.viewer.gui.SavePartialMzxmlDialog; import org.fhcrc.cpl.viewer.gui.WorkbenchFileChooser; import org.fhcrc.cpl.toolbox.gui.ScrollableImage; import org.fhcrc.cpl.toolbox.gui.ImagePanelLayer; import org.fhcrc.cpl.toolbox.gui.BaseImagePanelLayer; import org.fhcrc.cpl.toolbox.gui.ListenerHelper; import org.fhcrc.cpl.toolbox.ApplicationContext; import org.fhcrc.cpl.toolbox.TextProvider; import org.fhcrc.cpl.toolbox.datastructure.Tree2D; import org.apache.log4j.Logger; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.event.PopupMenuListener; import javax.swing.event.PopupMenuEvent; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.beans.PropertyChangeEvent; import java.io.File; import java.io.IOException; import java.text.NumberFormat; import java.util.ArrayList; import java.util.prefs.Preferences; /** * User: mbellew * Date: May 26, 2004 * Time: 10:06:01 AM */ public class MSImageComponent { protected static Logger _log = Logger.getLogger(MSImageComponent.class); public static String getPrefColorScheme() { String colorScheme = (String) ApplicationContext.getProperty(MSImageComponent.COLORSCHEME_PROPNAME); if (colorScheme == null) { Preferences prefs = Preferences.userNodeForPackage(Application.class); colorScheme = prefs.get(MSImageComponent.COLORSCHEME_PROPNAME, MSImageComponent.DEFAULT_COLORSCHEME); } return colorScheme; } public static void initializeColorMenu(JMenu colorMenu) { //if only one language supported, disable the menu and hide it for (String colorScheme : IntensityPlot.COLOR_SCHEMES) { JMenuItem menuItem = new JMenuItem(colorScheme); ColorActionListener listener = new ColorActionListener(colorScheme); menuItem.addActionListener(listener); colorMenu.add(menuItem); } } /** * actionlistener class for color menu items */ protected static class ColorActionListener implements ActionListener { String _colorScheme; public ColorActionListener(String colorScheme) { _colorScheme = colorScheme; } public void actionPerformed(ActionEvent event) { ApplicationContext.setProperty(MSImageComponent.COLORSCHEME_PROPNAME, _colorScheme); } } public static final int DEFAULT_FEATURE_SCAN_TOLERANCE = 3; public static final float DEFAULT_FEATURE_MZ_TOLERANCE = 3; public static final String COLORSCHEME_PROPNAME = "MSImageComponent.colorScheme"; public static final String DEFAULT_COLORSCHEME = "Fancy"; static Font _smallFont = Font.decode("Verdana PLAIN 9"); static Color _ruleColor = new Color(255, 255, 192); // plaf?? private JScrollPane scrollPane; private MSImagePanel imagePanel; private ColumnPanel columnPanel; private RowPanel rowPanel; private Rectangle highlightRect; // in mz/scan coordinates private int highlightScan = -1; private double[] highlightMasses = null; private java.util.List<FeatureSet> featureSets; private int ROWHEADER_WIDTH = 30; private int COLHEADER_HEIGHT = 25; private MSRun _run = null; private ListenerHelper helper = new ListenerHelper(this); //the current zoom level protected static double _zoomFactor = 1; //right-click context menu protected RightMousePopupMenu _rightMousePopupMenu = null; protected boolean _inZoomProcess = false; protected Point _currentMousePosition; //the effective size of the image panel, taking zoom into account Dimension _imagePanelSize; Point _lastMouseRightClick; java.util.List<ImagePanelLayer> imagePanelLayers = new ArrayList<ImagePanelLayer>(); //This single tree contains all features, from all _displayed_ featuresets. The two dimensions //are scan _index_ and m/z. This structure is ideal for quickly finding the closest //single feature to a given point (e.g., the mouse position). //For MS/MS features, the actual index is not stored here. Instead, the closest //lower single MS scan index is stored. protected Tree2D _allDisplayedFeaturesScanIndexMzTree; public MSImageComponent() { imagePanel = new MSImagePanel(); columnPanel = new ColumnPanel(); rowPanel = new RowPanel(); // columnPanel.setPreferredSize(new Dimension(640, COLHEADER_HEIGHT)); // rowPanel.setPreferredSize(new Dimension(ROWHEADER_WIDTH, 400)); helper.addListener(Application.getInstance(), "app_propertyChange"); EventQueue.invokeLater(new Runnable() { public void run() { setRun((MSRun)ApplicationContext.getProperty(SharedProperties.MS_RUN)); } }); _allDisplayedFeaturesScanIndexMzTree = new Tree2D(); } public MSImageComponent(Image image) { this(); setImage(image); } public void setRun(MSRun run) { if (_run == run) return; _run = run; if (null == run) setImage(null); else setImage(run.getImage(getPrefColorScheme())); } /** * Flashy transition to new zoom level. Swoopy * @param newZoomFactor */ public void transitionToZoomFactor(double newZoomFactor) { if (newZoomFactor == _zoomFactor) return; //more steps for more change in zoom level int numSteps=(int)(Math.abs(newZoomFactor - _zoomFactor) * 15); double startingZoomFactor = _zoomFactor; //indicate that this zoom step is an intermediate step _inZoomProcess=true; for (double i=1; i<= numSteps-1; i++) { double currentStep = startingZoomFactor + (i * ((newZoomFactor - startingZoomFactor) / numSteps)); setZoomFactor(currentStep); try { Thread.sleep(5); } catch (Exception e) {} } _inZoomProcess=false; setZoomFactor(newZoomFactor); _rightMousePopupMenu.updateZoomList(); } /** * Recenter on a feature, and populate the detail panes appropriately * @param feature */ public void selectFeature(Feature feature) { int scanNum = feature.getScan(); int scanIndex = scanNum; if (null != _run && scanNum <= _run.getScanCount()) { scanIndex = _run.getIndexForScanNum(scanNum); } float mz = feature.getMz(); ApplicationContext.setProperty(SharedProperties.SELECTED_POINT, feature); ApplicationContext.setProperty(SharedProperties.SELECTED, feature); if (null != _run) { int n = _run.getIndexForScanNum(feature.scan, true); ApplicationContext.setProperty(SharedProperties.MS_SCAN, _run.getScan(n)); } Point mousePoint = convertScanMzToMousePoint(scanIndex, mz); recenter((int) mousePoint.getX(), (int) mousePoint.getY()); imagePanel.selectPoint(scanIndex, mz); } /** * Tree2D doesn't allow node removal, so have to rebuild from the ground up. * MS2 scans are assigned the scan index of their _precursor scan_ */ protected void rebuildScanIndexMzTree() { MSRun run = (MSRun)ApplicationContext.getProperty(SharedProperties.MS_RUN); //if no run is loaded, nothing to do here if (run == null) return; _allDisplayedFeaturesScanIndexMzTree = new Tree2D(); for (FeatureSet featureSet : featureSets) { if (featureSet.isDisplayed()) { for (Feature feature : featureSet.getFeatures()) { int scanIndex = run.getIndexForScanNum(feature.getScan()); if (scanIndex < 0) { int ms2ScanIndex = run.getIndexForMS2ScanNum(feature.getScan()); if (ms2ScanIndex > 0) { MSRun.MSScan ms2Scan = run.getMS2Scan(ms2ScanIndex); int precursorScanNum = ms2Scan.getPrecursorScanNum(); if (precursorScanNum < 0) precursorScanNum = -precursorScanNum; int precursorScanIndex = run.getIndexForScanNum(precursorScanNum); if (precursorScanIndex < 0) precursorScanIndex = -precursorScanIndex; if (precursorScanIndex > run.getScanCount() - 1) continue; MSRun.MSScan precursorScan = run.getScan(precursorScanIndex); scanIndex = precursorScan.getIndex(); } else { scanIndex = -scanIndex; } } _allDisplayedFeaturesScanIndexMzTree.add(scanIndex, feature.getMz(), feature); } } } } /** * convert a point given by a MouseEvent into a scan and mz value, * returned as x and y of a new Point, respectively * @param mousePoint * @return */ public Point convertMousePointToScanIndexMz(Point mousePoint) { int x = (int) mousePoint.getX(); int y = (int) mousePoint.getY(); //scale to account for zoom factor x = (int) ( x / _zoomFactor); y = (int) ( y / _zoomFactor); y = imagePanel.getImageHeight() - y - 1; return new Point(x,y); } public Point convertMousePointToScanMz(Point mousePoint) { Point scanIndexMzPoint = convertMousePointToScanIndexMz(mousePoint); MSRun run = (MSRun) ApplicationContext.getProperty(SharedProperties.MS_RUN); MSRun.MSScan scan = run.getScan((int) scanIndexMzPoint.getX()); int scanNum = (int) scanIndexMzPoint.getX(); if (null != scan) { scanNum = scan.getNum(); } return new Point(scanNum, (int) scanIndexMzPoint.getY()); } /** * Convert a scan(index), mz pair to a mouse point * @param scanIndex * @param mz * @return */ public Point convertScanMzToMousePoint(int scanIndex, float mz) { int x = (int) (_zoomFactor * scanIndex); int y = (int) ((_zoomFactor * imagePanel.getImageHeight()) - (_zoomFactor * mz)); return new Point(x,y); } /** * Change the zoom level to a new value and redraw everything immediately. * Set the scrollbars appropriately * @param zoomFactor */ public void setZoomFactor(double zoomFactor) { if (zoomFactor == _zoomFactor) return; _zoomFactor = zoomFactor; _imagePanelSize = new Dimension((int)(_zoomFactor * imagePanel.getImageWidth()), (int)(_zoomFactor * imagePanel.getImageHeight())); imagePanel.invalidate(); // imagePanel.revalidate(); rowPanel.invalidate(); // rowPanel.revalidate(); columnPanel.invalidate(); // columnPanel.revalidate(); getScrollPane().invalidate(); getScrollPane().revalidate(); //calling paintImmediately() to facilitate swoopiness getScrollPane().paintImmediately(0,0,getScrollPane().getWidth(),getScrollPane().getHeight()); int horizScrollCenter = (int) (_zoomFactor * _lastMouseRightClick.getX()); int vertScrollCenter = (int) (_zoomFactor * _lastMouseRightClick.getY()); recenter(horizScrollCenter, vertScrollCenter); } /** * Recenter on the specified point, with x and y in mouse coordinates * @param x * @param y */ protected void recenter(int x, int y) { Rectangle viewportRect = getScrollPane().getViewportBorderBounds(); int horizScrollBarValue = x - (int) ((viewportRect.getWidth()) / 2); int vertScrollBarValue = y - (int) ((viewportRect.getHeight()) / 2); //for some reason it's necessary to call scrollRectToVisible() before setting //the scrollbar values. Otherwise, the scrollbar values get capped getScrollPane().getViewport().scrollRectToVisible(new Rectangle(horizScrollBarValue, vertScrollBarValue, 1,1)); getScrollPane().getHorizontalScrollBar().setValue(horizScrollBarValue); getScrollPane().getVerticalScrollBar().setValue(vertScrollBarValue); } //return the current zoom factor public static double getZoomFactor() { return _zoomFactor; } /** * Handling for property changes we care about: run, scan, zoom region, feature * ranges, highlighted features * @param event */ public void app_propertyChange(PropertyChangeEvent event) { String propName = event.getPropertyName(); if (SharedProperties.MS_RUN.equals(propName)) { MSRun run = (MSRun)event.getNewValue(); setRun(run); } else if (SharedProperties.MS_SCAN.equals(propName)) { if (null == event.getNewValue()) highlightScan = -1; else { MSRun.MSScan scan = (MSRun.MSScan)event.getNewValue(); highlightScan = scan.getIndex(); } imagePanel.repaint(); } else if (SharedProperties.ZOOM_REGION.equals(propName)) { if (null == event.getNewValue() || event.getNewValue() instanceof Rectangle) highlightRect = (Rectangle)event.getNewValue(); imagePanel.repaint(); } else if (SharedProperties.FEATURE_RANGES.equals(propName)) { featureSets = (java.util.List<FeatureSet>)event.getNewValue(); _log.debug("Got a FEATURE_RANGES app property event. New number of sets: " + featureSets.size()); int newSetsIndex = 0; for (int i=0; i<imagePanelLayers.size(); i++) { ImagePanelLayer oldLayer = imagePanelLayers.get(i); if (oldLayer instanceof MSImagePanel.FeatureSetImagePanelLayer) { if (i < featureSets.size() - 1) { int displayStyle = MSImagePanel.FeatureSetImagePanelLayer.DISPLAY_STYLE_X; if (0 == (i & 1)) displayStyle = MSImagePanel.FeatureSetImagePanelLayer.DISPLAY_STYLE_PLUS; MSImagePanel.FeatureSetImagePanelLayer newSetLayer = imagePanel.createFeatureSetLayer(featureSets.get(newSetsIndex++), "Feature Set " + newSetsIndex, displayStyle); imagePanelLayers.set(i, newSetLayer); } else { removeImagePanelLayer(i--); //if we just removed the last layer, we're done if (i >= imagePanelLayers.size() - 1) break; } } } for (int i=newSetsIndex; i<featureSets.size(); i++) { int displayStyle = MSImagePanel.FeatureSetImagePanelLayer.DISPLAY_STYLE_X; if (0 == (i & 1)) displayStyle = MSImagePanel.FeatureSetImagePanelLayer.DISPLAY_STYLE_PLUS; FeatureSet newFeatureSet = (FeatureSet) featureSets.get(newSetsIndex++); _log.debug("Displaying new feature set " + (i+1) + ", source file: " + newFeatureSet.getSourceFile().getAbsolutePath() + ", features: " + newFeatureSet.getFeatures().length); MSImagePanel.FeatureSetImagePanelLayer newSetLayer = imagePanel.createFeatureSetLayer(newFeatureSet, "Feature Set " + newSetsIndex, displayStyle); addImagePanelLayer(newSetLayer); } //rebuild the tree that holds all features' scan indices and m/z info rebuildScanIndexMzTree(); imagePanel.repaint(); } else if (SharedProperties.HIGHLIGHT_FEATURES.equals(propName) || SharedProperties.HIGHLIGHT_FEATURES2.equals(propName) || SharedProperties.HIGHLIGHT_FEATURES3.equals(propName)) { imagePanel.repaint(); } else if (MSImageComponent.COLORSCHEME_PROPNAME.equals(propName)) { Preferences prefs = Preferences.userNodeForPackage(Application.class); prefs.put(MSImageComponent.COLORSCHEME_PROPNAME, (String) ApplicationContext.getProperty(MSImageComponent.COLORSCHEME_PROPNAME)); if (_run != null) { _run.invalidateImage(); setImage(_run.getImage(getPrefColorScheme())); imagePanel.repaint(); } } } /** * Listens for events from a transparency slider and adjusts the transparency * of a layer accordingly */ protected class LayerAdjustmentListener implements AdjustmentListener { protected ImagePanelLayer layer = null; public LayerAdjustmentListener(ImagePanelLayer layer) { this.layer = layer; } public void adjustmentValueChanged(AdjustmentEvent e) { layer.setTranslucence(e.getValue()); imagePanel.repaint(); } } /** * Menu action. Shows sliders for adjusting the transparency of all * adjustable layers * * */ public void showLayerTransparencyDialog() { JDialog dialog = new JDialog(ApplicationContext.getFrame(), TextProvider.getText("LAYER_TRANSPARENCY")); dialog.setLocation(ApplicationContext.getFrame().getX() + 50, ApplicationContext.getFrame().getY() + 50); dialog.setLayout(new FlowLayout()); dialog.add(new JPanel()); for (ImagePanelLayer layer : imagePanelLayers) { JPanel scrollBarPanel = new JPanel(); scrollBarPanel.setLayout(new GridBagLayout()); scrollBarPanel.setSize(new Dimension(80,200)); JScrollBar layerScrollBar = new JScrollBar(); layerScrollBar.setMinimum(0); layerScrollBar.setMaximum(255); layerScrollBar.setPreferredSize(new Dimension(20,150)); layerScrollBar.setSize(new Dimension(20,150)); layerScrollBar.setMaximumSize(new Dimension(20,150)); layerScrollBar.setValue(layer.getTranslucence()); GridBagConstraints scrollBarGridBagConstraints = new GridBagConstraints(); scrollBarGridBagConstraints.gridwidth = GridBagConstraints.REMAINDER; scrollBarPanel.add(layerScrollBar, scrollBarGridBagConstraints); scrollBarPanel.add(new JLabel(layer.getName())); layerScrollBar.addAdjustmentListener(new LayerAdjustmentListener(layer)); dialog.add(scrollBarPanel); } dialog.add(new JPanel()); dialog.setSize(90 * imagePanelLayers.size() + 20,200); dialog.setVisible(true); } public void addImagePanelLayer(ImagePanelLayer newLayer) { imagePanelLayers.add(newLayer); ((WorkbenchFrame) ApplicationContext.getFrame()).showLayerTransparencyAction.setEnabled(true); } public void removeImagePanelLayer(int i) { imagePanelLayers.remove(i); if (imagePanelLayers.size() == 0) ((WorkbenchFrame) ApplicationContext.getFrame()).showLayerTransparencyAction.setEnabled(false); } public void setImage(Image image) { imagePanel.setImage(image); if (null != image) { _imagePanelSize = new Dimension((int)(_zoomFactor * image.getWidth(imagePanel)), (int)(_zoomFactor * image.getHeight(imagePanel))); } if (null != scrollPane) { rowPanel.revalidate(); columnPanel.revalidate(); imagePanel.revalidate(); //Make sure the scroll pane knows our size has changed scrollPane.revalidate(); scrollPane.paintImmediately(0,0,getScrollPane().getWidth(),getScrollPane().getHeight()); } } public MSImagePanel getImagePanel() { return imagePanel; } public JComponent getColumnPanel() { return columnPanel; } public JComponent getRowPanel() { return rowPanel; } public JScrollPane getScrollPane() { if (null == scrollPane) { setScrollPane(new JScrollPane()); } return scrollPane; } public void setScrollPane(JScrollPane scrollPane) { scrollPane.setViewportView(imagePanel); scrollPane.setColumnHeaderView(columnPanel); scrollPane.setRowHeaderView(rowPanel); // multiple monitors don't work with 'smart' scrolling scrollPane.getRowHeader().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); scrollPane.getColumnHeader().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); this.scrollPane = scrollPane; } /** * horizontal scale bar */ class ColumnPanel extends JComponent { ColumnPanel() { setBackground(new Color(0x00d0ffef)); } public Dimension getPreferredSize() { if (_imagePanelSize == null) return super.getPreferredSize(); return new Dimension(imagePanel.getWidth(),COLHEADER_HEIGHT); } public void setWidth(int width) { setPreferredSize(new Dimension(width, 20)); setSize(width, 20); invalidate(); } NumberFormat onedecimal = NumberFormat.getInstance(); { onedecimal.setMinimumFractionDigits(1); onedecimal.setMaximumFractionDigits(1); onedecimal.setGroupingUsed(false); } public void paint(Graphics graphics) { int height = COLHEADER_HEIGHT; int width = imagePanel.getImageWidth(); int viewWidth = (int) (_zoomFactor * width); graphics.setColor(_ruleColor); // taupe graphics.fillRect(0, 0, viewWidth, height); graphics.setColor(Color.BLACK); graphics.setFont(_smallFont); int distanceBetweenTicks = Math.max(1,(int) (10 / _zoomFactor)); for (int x = distanceBetweenTicks; x < width; x += distanceBetweenTicks) { int viewX = (int)(_zoomFactor * x); int index = x -1 ; if (0 == (x % (10 * distanceBetweenTicks))) { if (null != _run && x <= _run.getScanCount()) { MSRun.MSScan scan = _run.getScan(index); double t = scan.getDoubleRetentionTime(); String time = null; if (t <= 0.0) time = "" + scan.getNum(); else time = "" + onedecimal.format(new Double(t)); graphics.drawString(time, viewX - 7, height - 10); } } int viewIndex = (int) (_zoomFactor * index); if (0 == (x % (5 * distanceBetweenTicks))) graphics.drawLine(viewIndex, height - 7, viewIndex, height - 1); else graphics.drawLine(viewIndex, height - 4, viewIndex, height - 1); } } } /** * vertical scale bar */ class RowPanel extends JComponent { RowPanel() { // taupe more or less setBackground(new Color(0x00d0ffef)); } public void setHeight(int height) { setPreferredSize(new Dimension(ROWHEADER_WIDTH, height)); setSize(ROWHEADER_WIDTH, height); invalidate(); } public Dimension getPreferredSize() { if (_imagePanelSize == null) return super.getPreferredSize(); return new Dimension(ROWHEADER_WIDTH, imagePanel.getHeight()); } public void paint(Graphics graphics) { int height = imagePanel.getImageHeight(); int viewHeight = (int) (_zoomFactor * imagePanel.getImageHeight()); int width = ROWHEADER_WIDTH; graphics.setColor(_ruleColor); // taupe graphics.fillRect(0, 0, width, viewHeight); graphics.setColor(Color.BLACK); graphics.setFont(_smallFont); int distanceBetweenTicks = Math.max(1,(int) (50 / _zoomFactor)); for (int y = 0; y < height; y += distanceBetweenTicks) { int viewY = (int) (y * _zoomFactor); graphics.drawString("" + y, 0, viewHeight - viewY + 3); graphics.drawLine(width - 4, viewHeight - viewY - 1, width - 1, viewHeight - viewY - 1); } } } public class MSImagePanel extends ScrollableImage { public FeatureSetImagePanelLayer createFeatureSetLayer(FeatureSet featureSet, String name, int displayStyle) { return new MSImagePanel.FeatureSetImagePanelLayer(featureSet, name, displayStyle); } public class FeatureSetImagePanelLayer extends BaseImagePanelLayer { protected FeatureSet featureSet; protected int displayStyle = DISPLAY_STYLE_X; public static final int DISPLAY_STYLE_X = 0; public static final int DISPLAY_STYLE_PLUS = 1; public FeatureSetImagePanelLayer(FeatureSet newFeatureSet, String name, int newDisplayStyle) { _log.debug("FeatureSetImagePanelLayer: constructor, featureset name = " + name); featureSet = newFeatureSet; displayStyle = newDisplayStyle; setName(name); } public void draw(Graphics graphics, int imageWidth, int imageHeight, int minVisibleScan, int maxVisibleScan, double minVisibleMz, double maxVisibleMz) { //don't display feature sets during transition -- too slow _log.debug("FeatureSetImagePanelLayer.draw 1, name=" + getName()); if (_inZoomProcess || !featureSet.isDisplayed()) return; Feature[] featureRanges = featureSet.getFeatures(); if (featureRanges == null) return; graphics.setColor(adjustColor(featureSet.getColor())); _log.debug("FeatureSetImagePanelLayer.draw, beginning to draw " + featureRanges.length + " features."); for (Feature f : featureRanges) { if (f.mz < minVisibleMz || f.mz > maxVisibleMz) continue; // UNDONE: use retention time!!!! int scanIndex = _run.getIndexForScanNum(f.scan); scanIndex = scanIndex < 0 ? -(scanIndex+1) : scanIndex; if (scanIndex < minVisibleScan || scanIndex > maxVisibleScan || f.mz < minVisibleMz || f.mz > maxVisibleMz) continue; int y = imageHeight - (int)Math.floor(f.mz) - 1; if (IsotopicLabelExtraInfoDef.hasLabel(f) && IsotopicLabelExtraInfoDef.getLabelCount(f) > 0) { float mzHeavy = f.mz + (IsotopicLabelExtraInfoDef.getLabelCount(f) * IsotopicLabelExtraInfoDef.getLabel(f).getHeavy() / f.charge); int y2 = imageHeight - (int)Math.floor(mzHeavy) - 1; graphics.drawLine(scanIndex - 2, y, scanIndex + 2, y); graphics.drawLine(scanIndex - 1, y-1, scanIndex + 1, y-1); graphics.drawLine(scanIndex-2, y2, scanIndex+2, y2); graphics.drawLine(scanIndex-1, y2+1, scanIndex+1, y2+1); graphics.drawLine(scanIndex, y, scanIndex, y2); } else { switch (displayStyle) { case DISPLAY_STYLE_PLUS: // Draw "odd" sets as a '+' graphics.drawLine(scanIndex - 2, y, scanIndex + 2, y); graphics.drawLine(scanIndex, y+2, scanIndex, y-2); break; case DISPLAY_STYLE_X: // Draw "even" sets as a 'X' graphics.drawLine(scanIndex - 2, y-2, scanIndex + 2, y+2); graphics.drawLine(scanIndex - 2, y+2, scanIndex + 2, y-2); break; } } } } } /** * The topmost layer: highlight rectangle, highlighted scan, * mouse-selection rectangle. */ protected class TopPanelLayer extends BaseImagePanelLayer { protected final Color HIGHLIGHT_BASE_COLOR = new Color(Color.YELLOW.getRed(), Color.YELLOW.getGreen(), Color.YELLOW.getBlue(), 192); public TopPanelLayer() { setName("Top"); } public void draw(Graphics graphics, int imageWidth, int imageHeight, int minVisibleScan, int maxVisibleScan, double minVisibleMz, double maxVisibleMz) { if (null != highlightRect) { Rectangle viewRect = new Rectangle(highlightRect); viewRect.y = getImageHeight() - (highlightRect.y + highlightRect.height) - 1; //graphics.setColor(Color.RED); graphics.setColor(adjustColor(TRANSLUCENT_CYAN)); int x0 = viewRect.x, x1 = viewRect.x + viewRect.width - 1; int y0 = viewRect.y, y1 = viewRect.y + viewRect.height - 1; graphics.drawLine(x0, y0, x1, y0); graphics.drawLine(x0, y0+1, x0, y0+3); graphics.drawLine(x1, y0+1, x1, y0+3); graphics.drawLine(x0, y1, x1, y1); graphics.drawLine(x0, y1-1, x0, y1-3); graphics.drawLine(x1, y1-1, x1, y1-3); if (null != highlightMasses) { graphics.setColor(adjustColor(HIGHLIGHT_BASE_COLOR)); Rectangle bounds = graphics.getClipBounds(); for (int i = 0; i < highlightMasses.length; i++) graphics.drawLine((int)bounds.getMinX(), getImageHeight() - (int)highlightMasses[i] - 1, (int)bounds.getMaxX(), getImageHeight() - (int)highlightMasses[i] - 1); } } if (-1 != highlightScan) { int x = highlightScan; if (x >= 0 && x < getImageWidth()) { graphics.setColor(adjustColor(TRANSLUCENT_RED)); graphics.drawLine(x, 0, x, getImageHeight() - 1); } } //Draw the mouse selection rectangle if (mouseSelectionRect != null) { graphics.setColor(SELECTIONRECT_COLOR); graphics.drawRect((int) mouseSelectionRect.getX(), (int) (getImageHeight() - (mouseSelectionRect.getY() + mouseSelectionRect.getHeight())), (int) Math.abs(mouseSelectionRect.getWidth()), (int) Math.abs(mouseSelectionRect.getHeight())); graphics.setColor(SELECTIONRECT_FILLCOLOR); graphics.fillRect((int) mouseSelectionRect.getX(), (int) (getImageHeight() - (mouseSelectionRect.getY() + mouseSelectionRect.getHeight())), (int) Math.abs(mouseSelectionRect.getWidth()), (int) Math.abs(mouseSelectionRect.getHeight())); } //todo: this really should be tied to FeatureSets or done away with Feature[] highlightedFeatures = (Feature[]) ApplicationContext.getProperty(SharedProperties.HIGHLIGHT_FEATURES); highlightFeatures(graphics, highlightedFeatures, Color.ORANGE, minVisibleScan, maxVisibleScan, minVisibleMz, maxVisibleMz); Feature[] highlightedFeatures2 = (Feature[]) ApplicationContext.getProperty(SharedProperties.HIGHLIGHT_FEATURES2); highlightFeatures(graphics, highlightedFeatures2, Color.RED, minVisibleScan, maxVisibleScan, minVisibleMz, maxVisibleMz); Feature[] highlightedFeatures3 = (Feature[]) ApplicationContext.getProperty(SharedProperties.HIGHLIGHT_FEATURES3); highlightFeatures(graphics, highlightedFeatures3, Color.BLUE, minVisibleScan, maxVisibleScan, minVisibleMz, maxVisibleMz); } } protected ImagePanelLayer topPanelLayer = new TopPanelLayer(); boolean mouseOver = false; //These rectangles will both be in correct scanindex, mz coordinates Point mouseSelectionStartingPoint = null; public Rectangle mouseSelectionRect = null; protected boolean button1IsPressed = false; /** * Return the height of the image. This is important because it _doesn't_ * change with the zoom factor * @return */ public int getImageHeight() { if (getImage() == null) return super.getHeight(); return getImage().getHeight(this); } /** * Return the width of the image. This is important because it _doesn't_ * change with the zoom factor * @return */ public int getImageWidth() { if (getImage() == null) return super.getWidth(); return getImage().getWidth(this); } /** * This DOES scale with the zoom factor * @return */ public int getHeight() { return (int) getPreferredSize().getHeight(); } /** * This DOES scale with the zoom factor * @return */ public int getWidth() { return (int) getPreferredSize().getWidth(); } /** * This DOES scale with the zoom factor, which is necessary in order * to tell the scrollpane how big it is * @return */ public Dimension getPreferredSize() { if (_imagePanelSize == null) return super.getPreferredSize(); return _imagePanelSize; } /** * If there's a current mouse-selected rectangle, zoom to it */ public void zoomToSelection() { if (mouseSelectionRect == null) return; recenter((int) mouseSelectionRect.getCenterX(), (int) mouseSelectionRect.getCenterY()); Rectangle viewportRect = getScrollPane().getViewportBorderBounds(); double xZoomFactor = ((viewportRect.getWidth()-ROWHEADER_WIDTH) / getImageWidth()) / (Math.abs(mouseSelectionRect.getWidth() / getImageWidth())); double yZoomFactor = ((viewportRect.getHeight()-COLHEADER_HEIGHT) / getImageHeight()) / (Math.abs(mouseSelectionRect.getHeight() / getImageHeight())); double newZoomFactor = Math.min(xZoomFactor, yZoomFactor); transitionToZoomFactor(newZoomFactor); } public MSImagePanel() { super("MSImagePanel"); _rightMousePopupMenu = new RightMousePopupMenu(); _rightMousePopupMenu.setImagePanel(this); setComponentPopupMenu(_rightMousePopupMenu); //dragging listener for selection box creation this.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() { public void mouseMoved(MouseEvent event) { _currentMousePosition = event.getPoint(); } //drag selection is done with left button only public void mouseDragged(MouseEvent event) { if (button1IsPressed) { Point convertedEventPoint = convertMousePointToScanIndexMz(event.getPoint()); int topX = Math.min((int) convertedEventPoint.getX(), (int) mouseSelectionStartingPoint.getX()); int topY = Math.min((int) convertedEventPoint.getY(), (int) mouseSelectionStartingPoint.getY()); int width = (int) Math.abs(convertedEventPoint.getX() - mouseSelectionStartingPoint.getX()); int height = (int) Math.abs(convertedEventPoint.getY() - mouseSelectionStartingPoint.getY()); mouseSelectionRect = new Rectangle(topX, topY, width, height); repaint(); _rightMousePopupMenu.enableSelectionItems(); } } } ); this.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(MouseEvent event) { switch (event.getButton()) { case MouseEvent.BUTTON1: { Point scanIndexMzPoint = convertMousePointToScanIndexMz(event.getPoint()); selectPoint((int) scanIndexMzPoint.getX(), (float) scanIndexMzPoint.getY()); mouseSelectionRect = null; _rightMousePopupMenu.disableSelectionItems(); } default: { //popup menu is taken care of separately. //Only need to repaint to get rid of selection rectangle repaint(); } } } public void mouseEntered(MouseEvent event) { mouseOver = true; } public void mouseExited(MouseEvent event) { mouseOver = false; } public void mousePressed(MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1) { button1IsPressed = true; mouseSelectionStartingPoint = convertMousePointToScanIndexMz(event.getPoint()); // mouseSelectionRect = null; // _rightMousePopupMenu.disableSelectionItems(); } } public void mouseReleased(MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1) { button1IsPressed = false; } } }); // won't call getToolTipText() if we never setToolTipText(). setToolTipText(""); } public void saveMzXmlRegion() { if (mouseSelectionRect != null) { SavePartialMzxmlDialog saveDialog = new SavePartialMzxmlDialog(); //have to convert scan indices to scan numbers int lowScanIndex = (int) mouseSelectionRect.getX(); int highScanIndex = (int) (mouseSelectionRect.getX() + mouseSelectionRect.getWidth()); MSRun run = (MSRun) ApplicationContext.getProperty(SharedProperties.MS_RUN); //trim the scan window, just in case selection is out of whack, because //otherwise we get nullpointerexceptions lowScanIndex = Math.max(lowScanIndex, 0); highScanIndex = Math.min(highScanIndex, run.getScanCount() - 1); int lowScanNumber = run.getScan(lowScanIndex).getNum(); int highScanNumber = run.getScan(highScanIndex).getNum(); saveDialog.setRegionToSave(lowScanNumber, highScanNumber, (int) mouseSelectionRect.getY(), (int) (mouseSelectionRect.getY() + mouseSelectionRect.getHeight())); saveDialog.setVisible(true); } } /** * Select a point in the run -- populate the detail and spectrum windows appropriately * @param scanNumber * @param mz */ public void selectPoint(int scanNumber, float mz) { MSRun run = (MSRun) ApplicationContext.getProperty(SharedProperties.MS_RUN); if (null == run || scanNumber < 0 || scanNumber >= run.getScanCount()) return; MSRun.MSScan scan = run.getScan(scanNumber); ApplicationContext.setProperty(SharedProperties.MS_SCAN, scan); java.util.List<FeatureSet> featureSets = (java.util.List<FeatureSet>) ApplicationContext.getProperty(SharedProperties.FEATURE_RANGES); // Feature feat = FeatureSet.hitTest(featureSets, scanNumber, mz, 3, 3); Feature feat = getNearestFeature(scanNumber, mz, DEFAULT_FEATURE_SCAN_TOLERANCE, DEFAULT_FEATURE_MZ_TOLERANCE); if (null != feat) { ApplicationContext.setProperty(SharedProperties.SELECTED_POINT, feat); ApplicationContext.setProperty(SharedProperties.SELECTED, feat); } else ApplicationContext.setProperty(SharedProperties.SELECTED_POINT, new Spectrum.Peak(scan.getNum(), mz, 0.0F)); } /** * Figure out the right text to display, first accounting for the zoom * factor to figure out the _effective_ position of the mouse * @param event * @return */ public String getToolTipText(MouseEvent event) { Point scanMzPoint = convertMousePointToScanIndexMz(event.getPoint()); int scanIndex = (int) scanMzPoint.getX(); int mz = (int) scanMzPoint.getY(); Feature nearestFeature = getNearestFeature(scanIndex, mz, DEFAULT_FEATURE_SCAN_TOLERANCE, DEFAULT_FEATURE_MZ_TOLERANCE); if (null != nearestFeature) { String message = "(" + nearestFeature.scan + "," + nearestFeature.mz + ")" + " " + nearestFeature.getScanCount() + " scans " + nearestFeature.getScanFirst() + "-" + nearestFeature.getScanLast() + ", " + nearestFeature.intensity + "i " + nearestFeature.charge + "+"; return message; } MSRun run = (MSRun)ApplicationContext.getProperty(SharedProperties.MS_RUN); if (null != run && scanIndex >= 0 && scanIndex < run.getScanCount()) { MSRun.MSScan scan = run.getScan(scanIndex); if (null != scan) { String message = "(" + scan.getNum() + "," + mz + ") "; return message; } } return null; } /** * Find the nearest feature to the (scan, mz) position * * @param scanIndex * @param mz * @return */ public Feature getNearestFeature(int scanIndex, float mz, int scanTolerance, float mzTolerance) { Feature nearestFeature = null; if (null != featureSets) { java.util.List<Feature> nearbyFeatures = (java.util.List<Feature>) _allDisplayedFeaturesScanIndexMzTree.getPoints( scanIndex-scanTolerance, mz-mzTolerance, scanIndex+scanTolerance, mz+mzTolerance); if (nearbyFeatures.size() == 0) return null; double minDistance = Double.MAX_VALUE; Feature result = null; for (Feature feature : nearbyFeatures) { int featureScanIndex = _run.getIndexForScanNum(feature.scan); scanIndex = scanIndex < 0 ? -(scanIndex+1) : scanIndex; double distance = Math.sqrt(Math.pow(featureScanIndex - scanIndex, 2) * Math.pow(feature.mz - mz, 2)); if (distance < minDistance) { result = feature; minDistance = distance; } } return result; } return nearestFeature; } Color TRANSLUCENT_RED = new Color(1F, 0F, 0F, 0.5F); Color TRANSLUCENT_CYAN = new Color(0F, 1F, 1F, 0.5F); Color SELECTIONRECT_COLOR = new Color(0.4F, 1F, 0.4F, 0.8F); Color SELECTIONRECT_FILLCOLOR = new Color(0.3F, .9F, 0.3F, 0.1F); /** * Paint, scaling appropriately according to zoom * @param graphics */ public void paint(Graphics graphics) { paint(graphics, true); } /** * Paint the image window, being awfully darn careful about things * that need to get adjusted for zoom level * @param graphics * @param scaleWithZoom If true, scale according to zoom. Set to false * for saving an image to a file */ public void paint(Graphics graphics, boolean scaleWithZoom) { if (scaleWithZoom) ((Graphics2D) (graphics)).scale(_zoomFactor, _zoomFactor); super.paint(graphics); if (null == _run) return; Rectangle bounds = graphics.getClipBounds(); int minScan = null == bounds ? 0 : (int)bounds.getX(); int maxScan = null == bounds ? Integer.MAX_VALUE : (int)(minScan + bounds.getWidth()); double minMz = null == bounds ? 0 : getImageHeight() - bounds.getY() - bounds.getHeight(); double maxMz = null == bounds ? Integer.MAX_VALUE : getImageHeight() - bounds.getY(); for (ImagePanelLayer imagePanelLayer : imagePanelLayers) { imagePanelLayer.draw(graphics, getImageWidth(), getImageHeight(), minScan, maxScan, minMz, maxMz); } topPanelLayer.draw(graphics, getImageWidth(), getImageHeight(), minScan, maxScan, minMz, maxMz); } /** * draw circles around highlighted features in a given color * @param graphics * @param highlightedFeatures * @param color * @param minScan * @param maxScan * @param minMz * @param maxMz */ protected void highlightFeatures(Graphics graphics, Feature[] highlightedFeatures, Color color, int minScan, int maxScan, double minMz, double maxMz ) { if (null != highlightedFeatures) { //Clone so we don't have to reset line width Graphics2D g = (Graphics2D)graphics.create(); g.setColor(color); g.setStroke(new BasicStroke(2.0f)); for (int i = 0; i < highlightedFeatures.length; i++) { Feature feature = highlightedFeatures[i]; int scanIndex = _run.getIndexForScanNum(feature.scan); scanIndex = scanIndex < 0 ? -(scanIndex+1) : scanIndex; if (scanIndex < minScan || scanIndex > maxScan || feature.mz < minMz || feature.mz > maxMz) continue; g.drawOval(scanIndex - 8, getImageHeight() - (int)feature.mz - 9, 16, 16); } } } } public static class SaveImageAction extends AbstractAction { public SaveImageAction() { super("Save Image..."); } public void actionPerformed(ActionEvent e) { MSImageComponent comp = (MSImageComponent)ApplicationContext.getProperty("MSImageComponent"); if (null == comp) return; String name = ""; MSRun run = (MSRun)ApplicationContext.getProperty(SharedProperties.MS_RUN); if (null != run) { name = run.getFile().getPath(); if (name.toLowerCase().endsWith(".mzxml")) name = name.substring(0, name.length() - ".mzxml".length()); name += ".png"; } WorkbenchFileChooser chooser = new WorkbenchFileChooser(); if (0 < name.length()) chooser.setSelectedFile(new File(name)); int res = chooser.showSaveDialog(ApplicationContext.getFrame()); if (res != JFileChooser.APPROVE_OPTION) return; File f = chooser.getSelectedFile(); if (null == f) return; comp.saveImage(f); } } /** * Save the image to a file, no rescaling * @param f */ public void saveImage(File f) { saveImage(f, Integer.MAX_VALUE, Integer.MAX_VALUE, true); } /** * Saves the image to a file, rescaling the image if necessary, to make it fit both the maxWidth and * maxHeight constraints. * * DOES NOT distort the image * @param f * @param maxWidth * @param maxHeight */ public void saveImage(File f, int maxWidth, int maxHeight, boolean includeTIC) { BufferedImage checkedImage = (BufferedImage) imagePanel.getImage(); String ext = f.getName().substring(f.getName().lastIndexOf('.')+1).toLowerCase(); String formats[] = ImageIO.getWriterFormatNames(); setImage(checkedImage); String format = null; for (int i = 0; i < formats.length && null == format; i++) { if (formats[i].toLowerCase().equals(ext)) format = formats[i]; } if (null == format) { f = new File(f.getPath() + ".png"); format = "PNG"; } BufferedImage imageBW = (BufferedImage)this.imagePanel.getImage(); int width = imageBW.getWidth(); int height = imageBW.getHeight(); Image copy = null; if (null == featureSets) { copy = imageBW; // new BufferedImage(w, h, imageBW.getType()); // imageBW.copyData(copy.getRaster()); } else { // need to translate to color copy = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); Raster rBW = imageBW.getRaster(); WritableRaster rCopy = ((BufferedImage) copy).getRaster(); // could try to do this all at once, but it requires very large sample array // do row by row int[] samples = new int[width]; for (int y = 0 ; y<height ; y++) { rBW.getSamples(0, y, width, 1, 0, samples); rCopy.setSamples(0, y, width, 1, 0, samples); rCopy.setSamples(0, y, width, 1, 1, samples); rCopy.setSamples(0, y, width, 1, 2, samples); } // we don't want to draw highlights, save and restore these variables Rectangle r = highlightRect; int s = highlightScan; highlightRect = null; highlightScan = -1; // draw features imagePanel.paint(copy.getGraphics(), false); highlightRect = r; highlightScan = s; } //cut out the Total Ion Chromatogram if (!includeTIC) { float minMz = _run.getMzRange().min; CropImageFilter cif = new CropImageFilter(0, 0, width, (int) (height-minMz)); FilteredImageSource fis = new FilteredImageSource(copy.getSource(), cif); Image croppedImage = imagePanel.createImage(fis); int w = croppedImage.getWidth(null), h = croppedImage.getHeight(null); int type = BufferedImage.TYPE_INT_RGB; // many options copy = new BufferedImage(w, h, type); Graphics2D g2 = ((BufferedImage) copy).createGraphics(); g2.drawImage(croppedImage, 0, 0, null); g2.dispose(); } if (maxWidth < width || maxHeight < height) { if (maxWidth > width) maxWidth = width; if (maxHeight > height) maxHeight = height; ApplicationContext.setMessage("Rescaling image..."); float imageWidthHeightRatio = (float) width / (float) height; float specifiedWidthHeightRatio = (float) maxWidth / (float) maxHeight; int newWidth, newHeight; if (imageWidthHeightRatio > specifiedWidthHeightRatio) { newWidth = maxWidth; newHeight = (int) (newWidth / imageWidthHeightRatio); } else { newHeight = maxHeight; newWidth = (int) (imageWidthHeightRatio * (float) newHeight); } // imageToWrite = copy.getScaledInstance(newWidth, newHeight, Image.SCALE_DEFAULT); // copy = new BufferedImage(imageToWrite); BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); Graphics2D graphics2D = scaledImage.createGraphics(); graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics2D.drawImage(copy, 0, 0, newWidth, newHeight, null); copy = scaledImage; } try { ApplicationContext.setMessage("Writing image " + f.getPath() + "..."); IntensityPlot.writePlot(f, copy, format); ApplicationContext.setMessage("Successfully saved file " + f.getPath()); } catch (IOException x) { ApplicationContext.errorMessage(x.getMessage(), null); } } /** * Our context menu. There is a set of standard items that always comes up, related * to zoom and mzXML-saving. It is also possible to add feature-specific items that * come up when the user right-clicks close to a feature. * * This is done by analyzing the Extra Information Types that a feature has. Each * Extra Information Type may optionally provide MenuItems for a feature that will * show up here. E.g., an item that kicks off a Google search for the peptide. */ protected class RightMousePopupMenu extends JPopupMenu { //zoom level items JMenuItem item100, item25, item50, item150, item200, item300, item400; //mzxml-saving item JMenuItem itemSaveMzXml; //selection-zooming item JMenuItem itemZoomToSelection; Feature selectedFeature = null; java.util.List<Component> featureMenuItems = new ArrayList<Component>(); protected MSImagePanel _imagePanel = null; //for easy reference protected ArrayList<JMenuItem> _menuItems; public RightMousePopupMenu() { super(); item100 = new JMenuItem("100%"); item25 = new JMenuItem("25%"); item50 = new JMenuItem("50%"); item150 = new JMenuItem("150%"); item200 = new JMenuItem("200%"); item300 = new JMenuItem("300%"); item400 = new JMenuItem("400%"); itemSaveMzXml = new JMenuItem(TextProvider.getText("SAVE_MZXML_REGION")); itemZoomToSelection = new JMenuItem(TextProvider.getText("ZOOM_TO_SELECTION")); _menuItems = new ArrayList<JMenuItem>(); _menuItems.add(item100); _menuItems.add(item25); _menuItems.add(item50); _menuItems.add(item150); _menuItems.add(item200); _menuItems.add(item300); _menuItems.add(item400); _menuItems.add(itemSaveMzXml); _menuItems.add(itemZoomToSelection); item100.setActionCommand("100"); item25.setActionCommand("25"); item50.setActionCommand("50"); item150.setActionCommand("150"); item200.setActionCommand("200"); item300.setActionCommand("300"); item400.setActionCommand("400"); itemSaveMzXml.setActionCommand("save_mzxml"); itemZoomToSelection.setActionCommand("zoom_to_selection"); ListenerHelper helper = new ListenerHelper(this); for (int i=0; i<_menuItems.size(); i++) { helper.addListener(_menuItems.get(i),"menuItem_actionPerformed"); } //layout add(item100); add(new JPopupMenu.Separator()); add(item150); add(item200); add(item300); add(item400); add(new JPopupMenu.Separator()); add(item25); add(item50); add(itemZoomToSelection); add(new JPopupMenu.Separator()); add(itemSaveMzXml); //initially disable 100% zoom, because that's what we're at item100.setEnabled(false); //initially disable saving mzXml and zoom to region, since no region selected itemSaveMzXml.setEnabled(false); itemZoomToSelection.setEnabled(false); //for figuring out mouse location this.addPopupMenuListener(new RightPopupListener()); } public void setSelectedFeature(Feature feature) { selectedFeature = feature; } public void enableSelectionItems() { itemSaveMzXml.setEnabled(true); itemZoomToSelection.setEnabled(true); } public void disableSelectionItems() { itemSaveMzXml.setEnabled(false); itemZoomToSelection.setEnabled(false); } public void setImagePanel(MSImagePanel imagePanel) { _imagePanel = imagePanel; } /** * Remove all special items related to selected feature */ public void removeFeatureSpecificItems() { _menuItems.removeAll(featureMenuItems); for (Component component : featureMenuItems) remove(component); featureMenuItems = new ArrayList<Component>(); } /** * Add items related to the selected feature, by querying all extra info * types associated with the feature */ public void addFeatureSpecificItems() { for (FeatureExtraInformationDef extraInfoDef : selectedFeature.determineExtraInformationTypes()) { java.util.List<JMenuItem> thisDefMenuItems = extraInfoDef.createPopupMenuItems(selectedFeature); if (thisDefMenuItems != null && thisDefMenuItems.size() > 0) { Component separator = new JPopupMenu.Separator(); add(separator); featureMenuItems.add(separator); for (JMenuItem menuItem : thisDefMenuItems) { featureMenuItems.add(menuItem); add(menuItem); } } } } public void menuItem_actionPerformed(ActionEvent event) { if ("save_mzxml".equals(event.getActionCommand())) { _imagePanel.saveMzXmlRegion(); } else if ("zoom_to_selection".equals(event.getActionCommand())) { _imagePanel.zoomToSelection(); } else { int zoomFactorPercent = Integer.parseInt(event.getActionCommand()); transitionToZoomFactor(zoomFactorPercent * .01); } } /** * Set the correct disabled item representing the current zoom level */ public void updateZoomList() { String zoomFactorPercent = "" + (int)(_zoomFactor * 100); for (int i=0; i<_menuItems.size(); i++) { JMenuItem currentItem = _menuItems.get(i); if (zoomFactorPercent.equals(currentItem.getActionCommand())) currentItem.setEnabled(false); else currentItem.setEnabled(true); } } } /** * This is necessary because the automatic event listening supplied by * setComponentPopupMenu() cuts in front of the regular mouseclick listening * we're doing. Meaning that the only way to determine the location of * the mouseclick is by asking the popup menu itself. * * Also, here, we handle creation of special menu items related to the selected feature */ protected class RightPopupListener implements PopupMenuListener { /** * Can't ask for the location here because the menu isn't visible yet * @param e */ public void popupMenuWillBecomeVisible(PopupMenuEvent e) { Point convertedMousePoint = convertMousePointToScanIndexMz(_currentMousePosition); Feature nearestFeature = imagePanel.getNearestFeature((int) convertedMousePoint.getX(), (float) convertedMousePoint.getY(), DEFAULT_FEATURE_SCAN_TOLERANCE, DEFAULT_FEATURE_MZ_TOLERANCE); _rightMousePopupMenu.setSelectedFeature(nearestFeature); _rightMousePopupMenu.removeFeatureSpecificItems(); if (nearestFeature != null) { _rightMousePopupMenu.addFeatureSpecificItems(); } } /** * You'd think this would get kicked off _after_ the event processing * that we use to handle zoom, and therefore it'd be useless for storing * the mouseclick location. You'd be wrong. For some reason this happens * first. And a good thing, too! * @param e */ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { //get the absolute screen location int x = (int) _rightMousePopupMenu.getLocationOnScreen().getX(); int y = (int) _rightMousePopupMenu.getLocationOnScreen().getY(); //subtract out the upper-left-corner of the image panel x = x - (int) imagePanel.getLocationOnScreen().getX(); y = y - (int) imagePanel.getLocationOnScreen().getY(); //account for zoom x = (int) (x / _zoomFactor); y = (int) (y / _zoomFactor); _lastMouseRightClick = new Point(x,y); } /** * Nothin' * @param e */ public void popupMenuCanceled(PopupMenuEvent e) { } } }