/* * The MIT License (MIT) * * Copyright (c) 2007-2015 Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.broad.igv.ui.panel; import com.jidesoft.swing.JideSplitPane; import org.apache.log4j.Logger; import org.broad.igv.prefs.PreferencesManager; import org.broad.igv.session.Session; import org.broad.igv.track.AttributeManager; import org.broad.igv.ui.IGV; import org.broad.igv.ui.util.SnapshotUtilities; import org.broad.igv.ui.util.UIUtilities; import javax.swing.*; import java.awt.*; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.util.*; import java.util.List; import static org.broad.igv.prefs.Constants.*; /** * @author jrobinso * @date Sep 10, 2010 */ public class MainPanel extends JPanel implements Paintable { private static Logger log = Logger.getLogger(MainPanel.class); IGV igv; // private static final int DEFAULT_NAME_PANEL_WIDTH = 160; private int namePanelX; private int namePanelWidth = PreferencesManager.getPreferences().getAsInt(NAME_PANEL_WIDTH); private int attributePanelX; private int attributePanelWidth; private int dataPanelX; private int dataPanelWidth; public IGVPanel applicationHeaderPanel; public HeaderPanelContainer headerPanelContainer; private TrackPanelScrollPane dataTrackScrollPane; private TrackPanelScrollPane featureTrackScrollPane; private JideSplitPane centerSplitPane; private NameHeaderPanel nameHeaderPanel; private AttributeHeaderPanel attributeHeaderPanel; private int hgap = 5; private JScrollPane headerScrollPane; public MainPanel(IGV igv) { this.igv = igv; initComponents(); //Load IGV logo // try { // BufferedImage logo = ImageIO.read(getClass().getResource("resources/IGV_64.png")); // JLabel picLabel = new JLabel(new ImageIcon(logo)); // picLabel.setVerticalAlignment(SwingConstants.CENTER); // nameHeaderPanel.add(picLabel); // } catch (IOException e) { // //pass // } addComponentListener(new ComponentListener() { public void componentResized(ComponentEvent componentEvent) { revalidate(); repaint(); } public void componentMoved(ComponentEvent componentEvent) { //To change body of implemented methods use File | Settings | File Templates. } public void componentShown(ComponentEvent componentEvent) { //To change body of implemented methods use File | Settings | File Templates. } public void componentHidden(ComponentEvent componentEvent) { //To change body of implemented methods use File | Settings | File Templates. } }); } public void setDividerFractions(double[] fractions) { int[] dividerLocations = new int[fractions.length]; double h = centerSplitPane.getHeight(); for (int i = 0; i < fractions.length; i++) { dividerLocations[i] = (int) Math.round(h * fractions[i]); } centerSplitPane.setDividerLocations(dividerLocations); } public double[] getDividerFractions() { int[] dividerLocations = centerSplitPane.getDividerLocations(); double h = centerSplitPane.getHeight(); double[] dividerFractions = new double[dividerLocations.length]; for (int i = 0; i < dividerLocations.length; i++) { dividerFractions[i] = dividerLocations[i] / h; } return dividerFractions; } public void collapseNamePanel() { namePanelWidth = 0; revalidate(); } public void expandNamePanel() { namePanelWidth = PreferencesManager.getPreferences().getAsInt(NAME_PANEL_WIDTH); revalidate(); } public void setNamePanelWidth(int width) { this.namePanelWidth = width; revalidate(); } public void removeHeader() { remove(headerScrollPane); revalidate(); } public void restoreHeader() { add(headerScrollPane, BorderLayout.NORTH); revalidate(); } @Override public void doLayout() { layoutFrames(); super.doLayout(); applicationHeaderPanel.doLayout(); for (TrackPanel tp : getTrackPanels()) { tp.getScrollPane().doLayout(); } } @Override public void setBackground(Color color) { super.setBackground(color); if (headerPanelContainer != null) { applicationHeaderPanel.setBackground(color); nameHeaderPanel.setBackground(color); attributeHeaderPanel.setBackground(color); headerPanelContainer.setBackground(color); nameHeaderPanel.setBackground(color); attributeHeaderPanel.setBackground(color); for (TrackPanel tsp : getTrackPanels()) { tsp.setBackground(color); } } } private void initComponents() { setPreferredSize(new java.awt.Dimension(1021, 510)); setLayout(new java.awt.BorderLayout()); nameHeaderPanel = new NameHeaderPanel(); nameHeaderPanel.setBackground(new java.awt.Color(255, 255, 255)); nameHeaderPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); nameHeaderPanel.setMinimumSize(new java.awt.Dimension(0, 0)); nameHeaderPanel.setPreferredSize(new java.awt.Dimension(0, 0)); nameHeaderPanel.setLayout(new BorderLayout()); attributeHeaderPanel = new AttributeHeaderPanel(); attributeHeaderPanel.setBackground(new java.awt.Color(255, 255, 255)); attributeHeaderPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); attributeHeaderPanel.setDebugGraphicsOptions(javax.swing.DebugGraphics.NONE_OPTION); attributeHeaderPanel.setMinimumSize(new java.awt.Dimension(0, 0)); attributeHeaderPanel.setPreferredSize(new java.awt.Dimension(0, 0)); headerPanelContainer = new HeaderPanelContainer(); headerScrollPane = new JScrollPane(); headerScrollPane.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(102, 102, 102))); headerScrollPane.setForeground(new java.awt.Color(153, 153, 153)); headerScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); headerScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); headerScrollPane.setPreferredSize(new java.awt.Dimension(1021, 130)); add(headerScrollPane, java.awt.BorderLayout.NORTH); applicationHeaderPanel = new IGVPanel(this); applicationHeaderPanel.add(nameHeaderPanel); applicationHeaderPanel.add(attributeHeaderPanel); applicationHeaderPanel.add(headerPanelContainer); headerScrollPane.setViewportView(applicationHeaderPanel); dataTrackScrollPane = new TrackPanelScrollPane(); dataTrackScrollPane.setPreferredSize(new java.awt.Dimension(1021, 349)); final TrackPanel dataTrackPanel = new TrackPanel(IGV.DATA_PANEL_NAME, this); dataTrackScrollPane.setViewportView(dataTrackPanel); if (!PreferencesManager.getPreferences().getAsBoolean(SHOW_SINGLE_TRACK_PANE_KEY)) { featureTrackScrollPane = new TrackPanelScrollPane(); featureTrackScrollPane.setPreferredSize(new java.awt.Dimension(1021, 50)); featureTrackScrollPane.setViewportView(new TrackPanel(IGV.FEATURE_PANEL_NAME, this)); add(featureTrackScrollPane, java.awt.BorderLayout.SOUTH); } centerSplitPane = new SplitPane() { @Override public Insets getInsets(Insets insets) { return new Insets(0, 0, 0, 0); } }; centerSplitPane.setDividerSize(3); //centerSplitPane.setResizeWeight(0.5d); centerSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); centerSplitPane.add(dataTrackScrollPane, JSplitPane.TOP); if (!PreferencesManager.getPreferences().getAsBoolean(SHOW_SINGLE_TRACK_PANE_KEY)) { centerSplitPane.add(featureTrackScrollPane, JSplitPane.BOTTOM); } add(centerSplitPane, BorderLayout.CENTER); setBackground(PreferencesManager.getPreferences().getAsColor(BACKGROUND_COLOR)); } /** * Removes user added panels. Used for resetting sessions */ public void resetPanels() { for (TrackPanel tp : getTrackPanels()) { tp.clearTracks(); final TrackPanelScrollPane tsp = tp.getScrollPane(); if (tsp == dataTrackScrollPane || tsp == featureTrackScrollPane) { continue; } centerSplitPane.remove(tsp); TrackNamePanel.removeDropListenerFor(tsp.getNamePanel()); } igv.reset(); } /** * Add a new data panel set */ public synchronized TrackPanelScrollPane addDataPanel(String name) { final TrackPanel trackPanel = new TrackPanel(name, this); final TrackPanelScrollPane sp = new TrackPanelScrollPane(); Runnable runnable = () -> { sp.setViewportView(trackPanel); for (TrackPanel tp : getTrackPanels()) { tp.getScrollPane().minimizeHeight(); } // Insert the new panel just before the feature panel, or at the end if there is no feature panel. int featurePaneIdx = centerSplitPane.indexOfPane(featureTrackScrollPane); if (featurePaneIdx > 0) { centerSplitPane.insertPane(sp, featurePaneIdx); } else { centerSplitPane.add(sp); } if (!PreferencesManager.getPreferences().getAsBoolean(SHOW_SINGLE_TRACK_PANE_KEY)) { if (sp.getTrackPanel().getTracks().size() == 0) { //If the igv window is too small the divider won't exist and this causes an exception //We solved by setting a minimum size centerSplitPane.setDividerLocation(0, 3); } } }; UIUtilities.invokeAndWaitOnEventThread(runnable); return sp; } /** * Return an ordered list of TrackPanels. This method is provided primarily for storing sessions, where * TrackPanels need to be stored in proper order * * @return */ public java.util.List<TrackPanel> getTrackPanels() { ArrayList<TrackPanel> panels = new ArrayList<TrackPanel>(); for (Component c : centerSplitPane.getComponents()) { if (c instanceof TrackPanelScrollPane) { panels.add(((TrackPanelScrollPane) c).getTrackPanel()); } } return panels; } public void reorderPanels(java.util.List<String> names) { // First get visibile "heights" (distance between split pane dividers) int h = centerSplitPane.getHeight(); int[] dividerLocations = centerSplitPane.getDividerLocations(); Map<String, Integer> panelHeights = new HashMap(); int idx = 0; Map<String, TrackPanelScrollPane> panes = new HashMap(); for (Component c : centerSplitPane.getComponents()) { if (c instanceof TrackPanelScrollPane) { TrackPanelScrollPane tsp = (TrackPanelScrollPane) c; panes.put(tsp.getTrackPanelName(), tsp); int top = idx == 0 ? 0 : dividerLocations[idx - 1]; int bottom = idx < dividerLocations.length ? dividerLocations[idx] : h; panelHeights.put(tsp.getTrackPanelName(), (bottom - top)); idx++; } } // centerSplitPane.removeAll(); idx = 0; int divLoc = 0; for (String name : names) { centerSplitPane.add(panes.get(name)); if (idx < dividerLocations.length) { divLoc += panelHeights.get(name); dividerLocations[idx] = divLoc; idx++; } } centerSplitPane.setDividerLocations(dividerLocations); centerSplitPane.invalidate(); } public void tweakPanelDivider() { UIUtilities.invokeOnEventThread(new Runnable() { public void run() { // TODO Resize the data panel to make as much space as possible int h = centerSplitPane.getHeight(); int nPanes = centerSplitPane.getPaneCount(); double prefHeight = 0; for (int i = 0; i < nPanes; i++) { prefHeight += centerSplitPane.getPaneAt(i).getPreferredSize().getHeight(); } double ratio = h / prefHeight; int pos = 0; for (int i = 0; i < nPanes - 1; i++) { pos += (int) (ratio * centerSplitPane.getPaneAt(i).getPreferredSize().getHeight()); centerSplitPane.setDividerLocation(i, pos); } } }); } public void removeEmptyDataPanels() { List<TrackPanelScrollPane> emptyPanels = new ArrayList(); for (TrackPanel tp : getTrackPanels()) { if (tp.getTracks().isEmpty()) { emptyPanels.add(tp.getScrollPane()); } } for (TrackPanelScrollPane panel : emptyPanels) { if (panel != null) { centerSplitPane.remove(panel); TrackNamePanel.removeDropListenerFor(panel.getNamePanel()); } } } public void removeDataPanel(String name) { TrackPanelScrollPane sp = null; for (TrackPanel tp : getTrackPanels()) { if (name.equals(tp.getName())) { sp = tp.getScrollPane(); break; } } // Don't remove the "special" panes if (sp == dataTrackScrollPane || sp == featureTrackScrollPane) { return; } if (sp != null) { centerSplitPane.remove(sp); TrackNamePanel.removeDropListenerFor(sp.getNamePanel()); } } public void layoutFrames() { synchronized (getTreeLock()) { Insets insets = applicationHeaderPanel.getInsets(); namePanelX = insets.left; attributePanelX = namePanelX + namePanelWidth + hgap; attributePanelWidth = calculateAttributeWidth(); dataPanelX = attributePanelX + attributePanelWidth + hgap; java.util.List<ReferenceFrame> frames = FrameManager.getFrames(); dataPanelWidth = applicationHeaderPanel.getWidth() - insets.right - dataPanelX; if (frames.size() == 1) { frames.get(0).setBounds(0, dataPanelWidth); } else { float gap = Math.min(1, 20.0f / ((int) (1.5 * frames.size()))) * hgap; int x = 0; // Width is in floating point because we need to fill data panel, going straight to an "int" here // would cause truncation Session.GeneListMode mode = IGV.hasInstance() ? IGV.getInstance().getSession().getGeneListMode() : Session.GeneListMode.NORMAL; float wc = mode == Session.GeneListMode.NORMAL ? ((float) dataPanelWidth - (frames.size() - 1) * gap) / frames.size() : 20; for (int i = 0; i < frames.size(); i++) { ReferenceFrame frame = frames.get(i); int nextX = (int) ((i + 1) * (wc + gap)); int w = nextX - x; frame.setBounds(x, w); x = nextX; } } } } private int calculateAttributeWidth() { if (!PreferencesManager.getPreferences().getAsBoolean(SHOW_ATTRIBUTE_VIEWS_KEY)) { return 0; } Collection<String> attributeKeys = AttributeManager.getInstance().getVisibleAttributes(); int attributeCount = attributeKeys.size(); int packWidth = (attributeCount) * (AttributeHeaderPanel.ATTRIBUTE_COLUMN_WIDTH + AttributeHeaderPanel.COLUMN_BORDER_WIDTH) + AttributeHeaderPanel.COLUMN_BORDER_WIDTH; return packWidth; } public boolean isExpanded() { return namePanelWidth > 0; } public int getAttributePanelWidth() { return attributePanelWidth; } public int getNamePanelX() { return namePanelX; } public int getNamePanelWidth() { return namePanelWidth; } public int getAttributePanelX() { return attributePanelX; } public int getDataPanelX() { return dataPanelX; } public int getDataPanelWidth() { return dataPanelWidth; } public JideSplitPane getCenterSplitPane() { return centerSplitPane; } static class SplitPane extends JideSplitPane { @Override public void doLayout() { if (log.isTraceEnabled()) { log.trace("Layout"); } super.doLayout(); //To change body of overridden methods use File | Settings | File Templates. } } public void paintOffscreen(Graphics2D g, Rectangle rect) { // g.setColor(Color.lightGray); // g.fill(rect); // Header int width = applicationHeaderPanel.getWidth(); int height = applicationHeaderPanel.getHeight(); Graphics2D headerGraphics = (Graphics2D) g.create(); Rectangle headerRect = new Rectangle(0, 0, width, height); applicationHeaderPanel.paintOffscreen(headerGraphics, headerRect); headerGraphics.dispose(); // Now loop through track panel Rectangle r = centerSplitPane.getBounds(); g.translate(0, r.y); // Get the components of the center pane and sort by Y position. Component[] components = centerSplitPane.getComponents(); Arrays.sort(components, new Comparator<Component>() { public int compare(Component component, Component component1) { return component.getY() - component1.getY(); } }); int dy = components[0].getY(); for (Component c : components) { Graphics2D g2d = (Graphics2D) g.create(); g2d.translate(0, dy); if (c instanceof TrackPanelScrollPane) { TrackPanelScrollPane tsp = (TrackPanelScrollPane) c; //Skip if panel has no tracks if (tsp.getTrackPanel().getTracks().size() == 0) { continue; } int panelHeight = getOffscreenImagePanelHeight(tsp); Rectangle tspRect = new Rectangle(tsp.getBounds()); tspRect.height = panelHeight; g2d.setClip(new Rectangle(0, 0, tsp.getWidth(), tspRect.height)); tsp.paintOffscreen(g2d, tspRect); dy += tspRect.height; } else { g2d.setClip(new Rectangle(0, 0, c.getWidth(), c.getHeight())); c.paint(g2d); dy += c.getHeight(); } g2d.dispose(); } //super.paintBorder(g); } /** * Return the image height required to paint this component with current options. This is used to size bitmap * images for offscreen drawing. * * @return */ public int getOffscreenImageHeight() { int height = centerSplitPane.getBounds().y; for (Component c : centerSplitPane.getComponents()) { if (c instanceof TrackPanelScrollPane) { TrackPanelScrollPane tsp = (TrackPanelScrollPane) c; int panelHeight = getOffscreenImagePanelHeight(tsp); Rectangle tspRect = new Rectangle(tsp.getBounds()); tspRect.height = panelHeight; height += tspRect.height; } else { height += c.getHeight(); } } // TODO Not sure why this is neccessary height += 35; return height; } private int getOffscreenImagePanelHeight(TrackPanelScrollPane tsp) { int panelHeight; int maxPanelHeight = SnapshotUtilities.getMaxPanelHeight(); final int visibleHeight = tsp.getVisibleRect().height; if (maxPanelHeight < 0) { panelHeight = visibleHeight; } else { panelHeight = Math.min(maxPanelHeight, Math.max(visibleHeight, tsp.getDataPanel().getHeight())); } return panelHeight; } }