/* * 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 org.broad.igv.feature.Locus; import org.broad.igv.feature.genome.Genome; import org.broad.igv.lists.GeneList; import org.broad.igv.prefs.Constants; import org.broad.igv.prefs.PreferencesManager; import org.broad.igv.track.RegionScoreType; import org.broad.igv.track.Track; import org.broad.igv.ui.IGV; import org.broad.igv.ui.action.SearchCommand; import org.broad.igv.event.GenomeChangeEvent; import org.broad.igv.event.IGVEventBus; import org.broad.igv.event.IGVEventObserver; import org.broad.igv.ui.util.MessageUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * @author jrobinso * @date Sep 10, 2010 */ public class FrameManager implements IGVEventObserver { private static List<ReferenceFrame> frames = new ArrayList(); private static ReferenceFrame defaultFrame; public static final String DEFAULT_FRAME_NAME = "genome"; static { frames.add(getDefaultFrame()); } public synchronized static ReferenceFrame getDefaultFrame() { if (defaultFrame == null) { defaultFrame = new ReferenceFrame(DEFAULT_FRAME_NAME); } return defaultFrame; } public static List<ReferenceFrame> getFrames() { return frames; } public static ReferenceFrame getFrame(String frameName) { for (ReferenceFrame frame : frames) { if (frame.getName().equals(frameName)) { return frame; } } return null; } public static void setFrames(List<ReferenceFrame> f) { frames = f; IGVEventBus.getInstance().post(new ChangeEvent(frames)); } public static boolean isGeneListMode() { return frames.size() > 1; } public static int getStateHash() { if (isGeneListMode()) { String hs = ""; for (ReferenceFrame frame : frames) { hs = hs + frame.getStateHash(); } return hs.hashCode(); } else { return defaultFrame.getStateHash(); } } public static void setToDefaultFrame(String searchString) { frames.clear(); if (searchString != null) { Locus locus = getLocus(searchString, 0); if (locus != null) { getDefaultFrame().jumpTo(locus); } } frames.add(getDefaultFrame()); getDefaultFrame().recordHistory(); IGVEventBus.getInstance().post(new ChangeEvent(frames)); } private static boolean addNewFrame(String searchString) { boolean locusAdded = false; Locus locus = getLocus(searchString); if (locus != null) { ReferenceFrame referenceFrame = new ReferenceFrame(searchString); referenceFrame.jumpTo(locus); locusAdded = frames.add(referenceFrame); } return locusAdded; } public static void resetFrames(GeneList gl) { frames.clear(); if (gl == null) { frames.add(getDefaultFrame()); } else { List<String> lociNotFound = new ArrayList(); List<String> loci = gl.getLoci(); if (loci.size() == 1) { Locus locus = getLocus(loci.get(0)); if (locus == null) { lociNotFound.add(loci.get(0)); } else { IGV.getInstance().getSession().setCurrentGeneList(null); getDefaultFrame().jumpTo(locus.getChr(), locus.getStart(), locus.getEnd()); frames.add(getDefaultFrame()); } } else { for (String searchString : gl.getLoci()) { if (!addNewFrame(searchString)) { lociNotFound.add(searchString); } } } if (lociNotFound.size() > 1) { StringBuffer message = new StringBuffer(); message.append("<html>The following loci could not be found in the currently loaded annotation sets: <br>"); for (String s : lociNotFound) { message.append(s + " "); } MessageUtils.showMessage(message.toString()); } } IGVEventBus.getInstance().post(new ChangeEvent(frames)); } /** * @return The minimum scale among all active frames * TODO -- track this with "rescale" events, rather than compute on the fly */ public static double getMinimumScale() { double minScale = Double.MAX_VALUE; for (ReferenceFrame frame : frames) { minScale = Math.min(minScale, frame.getScale()); } return minScale; } /** * Uses default flanking region with * {@link #getLocus(String, int)} * * @param searchString * @return */ public static Locus getLocus(String searchString) { int flankingRegion = PreferencesManager.getPreferences().getAsInt(Constants.FLANKING_REGION); return getLocus(searchString, flankingRegion); } /** * Runs a search for the specified string, and returns a locus * of the given region with additional space on each side. * Note: We DO NOT add the flanking region if the {@code searchString} * is a locus (e.g. chr1:50-100), only if it's a gene or feature name (or something else) * * @param searchString * @param flankingRegion * @return The found locus, null if not found */ public static Locus getLocus(String searchString, int flankingRegion) { SearchCommand cmd = new SearchCommand(getDefaultFrame(), searchString); List<SearchCommand.SearchResult> results = cmd.runSearch(searchString); Locus locus = null; for (SearchCommand.SearchResult result : results) { if (result.getType() != SearchCommand.ResultType.ERROR) { int delta = 0; if (result.getType() != SearchCommand.ResultType.LOCUS) { if (flankingRegion < 0) { delta = (-flankingRegion * (result.getEnd() - result.getStart())) / 100; } else { delta = flankingRegion; } } int start = result.getStart() - delta; //Don't allow flanking region to extend past origin //There are some circumstances in which we render before origin (e.g. soft-clips) //so we are conservative if (start < 0 && result.getStart() >= -1) { start = 0; } locus = new Locus( result.getChr(), start, result.getEnd() + delta); //We just take the first result break; } } return locus; } public static void removeFrame(ReferenceFrame frame) { frames.remove(frame); } public static void sortFrames(final Track t) { Collections.sort(frames, new Comparator<ReferenceFrame>() { @Override public int compare(ReferenceFrame o1, ReferenceFrame o2) { float s1 = t.getRegionScore(o1.getChromosome().getName(), (int) o1.getOrigin(), (int) o1.getEnd(), o1.getZoom(), RegionScoreType.SCORE, o1.getName()); float s2 = t.getRegionScore(o2.getChromosome().getName(), (int) o2.getOrigin(), (int) o2.getEnd(), o2.getZoom(), RegionScoreType.SCORE, o2.getName()); return (s1 == s2 ? 0 : (s1 > s2) ? -1 : 1); } }); } /** * Increment the zoom level of the visibile frame(s). Supports batch commands zoomIn and zoomOut * * @param zoom the zoom level increment, usually -1 or 1 */ public static void incrementZoom(int zoom) { if (isGeneListMode()) { for (ReferenceFrame frame : getFrames()) { frame.doZoomIncrement(zoom); } } else { getDefaultFrame().doZoomIncrement(zoom); } } @Override public void receiveEvent(Object event) { if (event instanceof GenomeChangeEvent) { Genome newGenome = ((GenomeChangeEvent) event).genome; boolean force = true; getDefaultFrame().setChromosomeName(newGenome.getHomeChromosome(), force); } } public static class ChangeEvent { List<ReferenceFrame> frames; public ChangeEvent(List<ReferenceFrame> frames) { this.frames = frames; } public List<ReferenceFrame> getFrames() { return frames; } } }