Java tutorial
/** * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package savant.view.swing; import java.awt.Color; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.*; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ut.biolab.savant.analytics.savantanalytics.AnalyticsAgent; import savant.api.adapter.TrackAdapter; import savant.api.data.DataFormat; import savant.api.util.DialogUtils; import savant.api.util.Resolution; import savant.controller.GenomeController; import savant.controller.LocationController; import savant.data.sources.DataSource; import savant.data.types.Genome; import savant.settings.InterfaceSettings; import savant.settings.SettingsDialog; import savant.util.DrawingMode; import savant.util.MiscUtils; import savant.view.dialog.BAMFilterDialog; import savant.view.tracks.BAMTrack; import savant.view.tracks.RichIntervalTrack; import savant.view.tracks.SequenceTrack; import savant.view.tracks.Track; /** * Menu-bar which appears in the top-right of each Frame, adjusted depending on the track type. * * @author tarkvara */ public final class FrameCommandBar extends JMenuBar { private static final Log LOG = LogFactory.getLog(FrameCommandBar.class); /** Possible interval heights available to interval renderers. */ private static final int[] AVAILABLE_INTERVAL_HEIGHTS = new int[] { 1, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40 }; /** The Frame on which this menu-bar appears. */ private final Frame frame; /** GraphPane associated with our Frame. */ private final GraphPane graphPane; /** * The track associated with our frame. For simplicity's sake, we only care about the first track (e.g. * we consider only the BAM track and ignore the coverage track). */ private final Track mainTrack; private JMenuItem scaleToFitItem; private JCheckBoxMenuItem[] modeItems; private int drawModePosition = 0; private JMenu intervalMenu; private JSlider intervalSlider; private JCheckBoxMenuItem setAsGenomeButton; private JCheckBoxMenuItem baseQualityItem, mappingQualityItem; /** * Create command bar */ FrameCommandBar(Frame f) { frame = f; graphPane = (GraphPane) f.getGraphPane(); mainTrack = (Track) f.getTracks()[0]; setMinimumSize(new Dimension(50, 22)); setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY)); // TODO: Should we really be doing BAM-specific stuff in this class? JMenu toolsMenu = createToolsMenu(); add(toolsMenu); if (mainTrack.getValidDrawingModes().length > 1) { JMenu modeMenu = createDisplayModeMenu(); add(modeMenu); } JMenu appearanceMenu = createAppearanceMenu(); add(appearanceMenu); DataFormat df = mainTrack.getDataFormat(); if (df == DataFormat.ALIGNMENT || df == DataFormat.GENERIC_INTERVAL || df == DataFormat.RICH_INTERVAL) { intervalMenu = createIntervalMenu(); add(intervalMenu); drawModeChanged(mainTrack.getDrawingMode()); int h = getIntervalHeight(); graphPane.setUnitHeight(h); graphPane.setScaledToFit(false); } } /** * Create Tools menu for commandBar. This is common to all track types. */ private JMenu createToolsMenu() { JMenu menu = new JMenu("Tools"); JMenuItem item = new JCheckBoxMenuItem("Lock X Axis"); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { graphPane.setLocked(!graphPane.isLocked()); } }); menu.add(item); // TODO: experimental feature which doesn't quite work yet final JMenuItem itemy = new JCheckBoxMenuItem("Lock Y Axis"); itemy.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Trying to locking Y max: " + !itemy.isSelected()); graphPane.setYMaxLocked(itemy.isSelected()); } }); //menu.add(itemy); item = new JMenuItem("Copy URL to Clipboard"); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(new StringSelection(mainTrack.getDataSource().getURI().toString()), null); } }); menu.add(item); DataFormat df = mainTrack.getDataFormat(); if (df == DataFormat.SEQUENCE) { menu.add(new JSeparator()); JMenuItem copyItem = new JMenuItem("Copy Sequence to Clipboard"); copyItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { try { LocationController lc = LocationController.getInstance(); byte[] seq = ((SequenceTrack) mainTrack).getSequence(lc.getReferenceName(), lc.getRange()); Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(new StringSelection(new String(seq)), null); } catch (Throwable x) { LOG.error(x); DialogUtils.displayError("Unable to copy sequence to clipboard."); } } }); menu.add(copyItem); setAsGenomeButton = new JCheckBoxMenuItem("Set as Genome"); setAsGenomeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Genome newGenome = Genome.createFromTrack(mainTrack); GenomeController.getInstance().setGenome(newGenome); } }); menu.add(setAsGenomeButton); menu.addMenuListener(new MenuAdapter() { @Override public void menuSelected(MenuEvent me) { Track seqTrack = (Track) GenomeController.getInstance().getGenome().getSequenceTrack(); if (seqTrack == mainTrack) { setAsGenomeButton.setSelected(true); setAsGenomeButton.setEnabled(false); setAsGenomeButton.setToolTipText("This track is already the reference sequence"); } else { setAsGenomeButton.setSelected(false); setAsGenomeButton.setEnabled(true); setAsGenomeButton.setToolTipText("Use this track as the reference sequence"); } } }); } else if (df == DataFormat.ALIGNMENT) { menu.add(new JSeparator()); item = new JMenuItem("Filter..."); item.setToolTipText("Control how records are filtered"); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new BAMFilterDialog(DialogUtils.getMainWindow(), (BAMTrack) mainTrack).setVisible(true); } }); menu.add(item); } else if (df == DataFormat.RICH_INTERVAL) { menu.add(new JSeparator()); final JMenuItem bookmarkAll = new JMenuItem("Bookmark All Features"); bookmarkAll.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DataSource ds = (DataSource) mainTrack.getDataSource(); if (DialogUtils.askYesNo("Bookmark All Features ", String.format("This will create %d bookmarks. Are you sure you want to do this?", ds.getDictionaryCount())) == DialogUtils.YES) { ds.addDictionaryToBookmarks(); Savant.getInstance().displayBookmarksPanel(); } } }); menu.add(bookmarkAll); menu.addMenuListener(new MenuAdapter() { @Override public void menuSelected(MenuEvent me) { bookmarkAll.setEnabled(mainTrack.getDataSource() instanceof DataSource && ((DataSource) mainTrack.getDataSource()).getDictionaryCount() > 0); } }); } return menu; } /** * Create display menu for commandBar */ private JMenu createDisplayModeMenu() { JMenu menu = new JMenu("Display Mode"); //display modes DrawingMode[] validModes = mainTrack.getValidDrawingModes(); modeItems = new JCheckBoxMenuItem[validModes.length]; for (int i = 0; i < validModes.length; i++) { JCheckBoxMenuItem item = new JCheckBoxMenuItem(validModes[i].getDescription()); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource(); if (item.getState()) { DrawingMode[] validModes = mainTrack.getValidDrawingModes(); for (int j = 0; j < modeItems.length; j++) { if (item.getText().equals(validModes[j].getDescription())) { for (TrackAdapter t : frame.getTracks()) { AnalyticsAgent.log(new NameValuePair[] { new NameValuePair("track-event", "DisplayModeChanged"), new NameValuePair("track-type", t.getClass().getSimpleName()), new NameValuePair("target-mode", validModes[j].getDescription()) }); t.setDrawingMode(validModes[j]); } drawModePosition = j; } else { modeItems[j].setState(false); } } } else { item.setState(true); } } }); if (validModes[i] == mainTrack.getDrawingMode()) { item.setState(true); } modeItems[i] = item; menu.add(item); } // Determine position of current draw mode. DrawingMode currentMode = mainTrack.getDrawingMode(); for (int i = 0; i < validModes.length; i++) { if (validModes[i].equals(currentMode)) { drawModePosition = i; break; } } // Allow cycling through display modes. graphPane.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { //check for: Mac + Command + 'm' OR !Mac + Ctrl + 'm' if ((MiscUtils.MAC && e.getModifiersEx() == 256 && e.getKeyChar() == 'm') || (!MiscUtils.MAC && e.getKeyChar() == '\n' && e.isControlDown())) { cycleDisplayMode(); } } }); return menu; } private void cycleDisplayMode() { if (modeItems == null) return; modeItems[drawModePosition].setState(false); drawModePosition++; DrawingMode[] drawModes = mainTrack.getValidDrawingModes(); if (drawModePosition >= drawModes.length) drawModePosition = 0; modeItems[drawModePosition].setState(true); mainTrack.setDrawingMode(drawModes[drawModePosition]); } private JMenu createAppearanceMenu() { JMenu menu = new JMenu("Appearance"); JMenuItem item = new JMenuItem("Colour Settings..."); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { SettingsDialog dlg = new SettingsDialog(DialogUtils.getMainWindow(), "Colour Settings", new TrackColourSchemePanel(mainTrack)); dlg.setVisible(true); } }); menu.add(item); DataFormat df = mainTrack.getDataFormat(); if (df != DataFormat.SEQUENCE) { scaleToFitItem = new JCheckBoxMenuItem("Scale to Fit"); scaleToFitItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { if (scaleToFitItem.isSelected()) { graphPane.setScaledToFit(true); } else { // This check is kinda ugly, but we only want to set the interval height from the slider // if we're showing intervals (i.e. not arc mode and not coverage). if (intervalSlider != null && mainTrack.getDrawingMode() != DrawingMode.ARC && mainTrack.getDrawingMode() != DrawingMode.ARC_PAIRED && mainTrack.getResolution( LocationController.getInstance().getRange()) == Resolution.HIGH) { int h = getIntervalHeight(); graphPane.setUnitHeight(h); } graphPane.setScaledToFit(false); } } }); scaleToFitItem .setToolTipText("If selected, the track's display will be scaled to fit the available height."); menu.addMenuListener(new MenuAdapter() { @Override public void menuSelected(MenuEvent me) { scaleToFitItem.setSelected(graphPane.isScaledToFit()); } }); menu.add(scaleToFitItem); } if (df == DataFormat.RICH_INTERVAL) { menu.add(new JSeparator()); JCheckBoxMenuItem itemRGB = new JCheckBoxMenuItem("Enable ItemRGB"); itemRGB.setState(false); itemRGB.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ((RichIntervalTrack) mainTrack).toggleItemRGBEnabled(); graphPane.setRenderForced(); graphPane.repaint(); } }); menu.add(itemRGB); JCheckBoxMenuItem score = new JCheckBoxMenuItem("Enable Score"); score.setState(false); score.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ((RichIntervalTrack) mainTrack).toggleScoreEnabled(); graphPane.setRenderForced(); graphPane.repaint(); } }); menu.add(score); JCheckBoxMenuItem alternate = new JCheckBoxMenuItem("Display Alternate Name"); alternate.setState(((RichIntervalTrack) mainTrack).isUsingAlternateName()); alternate.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ((RichIntervalTrack) mainTrack).toggleAlternateName(); graphPane.setRenderForced(); graphPane.repaint(); } }); menu.add(alternate); } else if (df == DataFormat.ALIGNMENT) { menu.add(new JSeparator()); baseQualityItem = new JCheckBoxMenuItem("Enable Base Quality"); baseQualityItem.setState(false); baseQualityItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (((BAMTrack) mainTrack).toggleBaseQualityEnabled()) { mappingQualityItem.setState(false); } graphPane.setRenderForced(); graphPane.repaint(); } }); menu.add(baseQualityItem); mappingQualityItem = new JCheckBoxMenuItem("Enable Mapping Quality"); mappingQualityItem.setState(false); mappingQualityItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (((BAMTrack) mainTrack).toggleMappingQualityEnabled()) { baseQualityItem.setState(false); } graphPane.setRenderForced(); graphPane.repaint(); } }); menu.add(mappingQualityItem); } return menu; } /** * Create interval height slider for commandBar */ private JMenu createIntervalMenu() { JMenu menu = new JMenu("Interval Height"); intervalSlider = new JSlider(JSlider.VERTICAL, 1, AVAILABLE_INTERVAL_HEIGHTS.length, 1); intervalSlider.setMinorTickSpacing(1); intervalSlider.setMajorTickSpacing(AVAILABLE_INTERVAL_HEIGHTS.length / 2); intervalSlider.setSnapToTicks(true); intervalSlider.setPaintTicks(true); intervalSlider.setValue( getSliderFromIntervalHeight(InterfaceSettings.getIntervalHeight(mainTrack.getDataFormat()))); intervalSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int h = getIntervalHeight(); if (graphPane.isScaledToFit()) { graphPane.setUnitHeight(h); graphPane.setScaledToFit(false); // Forces rerender and repaint internally. } else if (graphPane.getUnitHeight() != h) { graphPane.setUnitHeight(h); graphPane.setRenderForced(); graphPane.repaint(); } } }); menu.add(intervalSlider); return menu; } /** * We don't want our menu-bar to get squozen, so make its minimum size the preferred size. */ @Override public Dimension getMinimumSize() { return getPreferredSize(); } /** * Get the unit-height which corresponds to the current position of the interval slider. */ int getIntervalHeight() { int slider = intervalSlider.getValue() - 1; //starts at 1 if (slider < 0) { return AVAILABLE_INTERVAL_HEIGHTS[0]; } else if (slider >= AVAILABLE_INTERVAL_HEIGHTS.length) { return AVAILABLE_INTERVAL_HEIGHTS[AVAILABLE_INTERVAL_HEIGHTS.length - 1]; } else { return AVAILABLE_INTERVAL_HEIGHTS[slider]; } } /** * Given an interval height, determine the slider value which corresponds to it. * @return */ private static int getSliderFromIntervalHeight(int intervalHeight) { int newValue = 0; int diff = Math.abs(AVAILABLE_INTERVAL_HEIGHTS[0] - intervalHeight); for (int i = 1; i < AVAILABLE_INTERVAL_HEIGHTS.length; i++) { int currVal = AVAILABLE_INTERVAL_HEIGHTS[i]; int currDiff = Math.abs(currVal - intervalHeight); if (currDiff < diff) { newValue = i; diff = currDiff; } } return newValue + 1; //can't be 0 } public void drawModeChanged(DrawingMode mode) { if (intervalMenu != null) { intervalMenu.setVisible(mode != DrawingMode.ARC && mode != DrawingMode.ARC_PAIRED && mode != DrawingMode.SNP && mode != DrawingMode.STRAND_SNP); } } /** * We're generally only interested in menuSelected. */ private abstract class MenuAdapter implements MenuListener { @Override public void menuDeselected(MenuEvent me) { } @Override public void menuCanceled(MenuEvent me) { } } }