/* * 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. */ /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.broad.igv.prefs; import org.apache.log4j.Logger; import org.broad.igv.DirectoryManager; import org.broad.igv.Globals; import org.broad.igv.feature.genome.GenomeListItem; import org.broad.igv.renderer.ColorScaleFactory; import org.broad.igv.renderer.ContinuousColorScale; import org.broad.igv.track.TrackType; import org.broad.igv.ui.IGV; import org.broad.igv.ui.UIConstants; import org.broad.igv.ui.color.ColorUtilities; import org.broad.igv.ui.color.PaletteColorTable; import org.broad.igv.event.AlignmentTrackEvent; import org.broad.igv.event.IGVEventBus; import org.broad.igv.util.HttpUtils; import java.awt.*; import java.io.*; import java.util.*; import static org.broad.igv.prefs.Constants.*; /** * Manages user preferences. */ public class IGVPreferences { private static Logger log = Logger.getLogger(IGVPreferences.class); IGVPreferences parent; Map<String, String> userPreferences; // Preferences which should persist for this session only Set<String> overrideKeys = new HashSet<>(); // Cached non-string preference values private Map<String, Boolean> booleanCache = new Hashtable(); private Map<String, Object> objectCache = new Hashtable(); private Map<TrackType, ContinuousColorScale> colorScaleCache = new Hashtable(); private PaletteColorTable mutationColorScheme = null; public IGVPreferences() { this.userPreferences = new HashMap<>(); } public IGVPreferences(Map<String, String> userPreferences, IGVPreferences parent) { this.parent = parent; this.userPreferences = userPreferences == null ? new HashMap<>() : userPreferences; } public String get(String key) { key = key.trim(); if(userPreferences.containsKey(key)) { return userPreferences.get(key); } else if(parent != null) { return parent.get(key); } else { return null; } } /** * Return preference with given key and specified default value. If key is not present defaultValue is returned, * no search through defaults or hierarchy is performed. * @param key * @param defaultValue * @return */ public String get(String key, String defaultValue) { key = key.trim(); String val = userPreferences.get(key); return val == null ? defaultValue : val; } /** * Return the preference as a boolean value. * * @param key * @return */ public boolean getAsBoolean(String key) { key = key.trim(); Boolean boolValue = booleanCache.get(key); if (boolValue == null) { String value = get(key); if (value == null) { log.error("No default value for: " + key); return false; } boolValue = new Boolean(get(key, value)); booleanCache.put(key, boolValue); } return boolValue.booleanValue(); } /** * Return the preference as an integer. * * @param key * @return */ public int getAsInt(String key) { key = key.trim(); Number value = (Number) objectCache.get(key); if (value == null) { String defValue = get(key); if (defValue == null) { log.error("No default value for: " + key); return 0; } value = new Integer(get(key, defValue)); objectCache.put(key, value); } return value.intValue(); } /** * Return the preference as a color. * * @param key * @return */ public Color getAsColor(String key) { key = key.trim(); Color value = (Color) objectCache.get(key); if (value == null) { String defValue = get(key); if (defValue == null) { log.error("No default value for: " + key); return Color.white; } value = ColorUtilities.stringToColor(defValue); objectCache.put(key, value); } return value; } /** * Return the preference as an float. * * @param key * @return */ public float getAsFloat(String key) { key = key.trim(); Number value = (Number) objectCache.get(key); if (value == null) { String defValue = get(key); if (defValue == null) { log.error("No default value for: " + key); return 0; } value = new Float(get(key, defValue)); objectCache.put(key, value); } return value.floatValue(); } /** * Get a property which is a delimited list of entries * * @param key * @return The string array of tokens, or an empty array if not present */ private String[] getAsArray(String key) { String stringProp = get(key); if (stringProp == null) { return new String[0]; } else { return stringProp.split(Globals.HISTORY_DELIMITER); } } public boolean hasExplicitValue(String key) { key = key.trim(); return userPreferences.containsKey(key); } /** * Get the default value for the specified key. * May be null. * * @param key * @return */ public String getDefaultValue(String key) { key = key.trim(); return parent == null ? get(key) : parent.get(key); } public void addOverrides(Map<String, String> newPrefs) { overrideKeys.addAll(newPrefs.keySet()); userPreferences.putAll(newPrefs); } /** * Update any cached values with the new key/value pair * * @param key * @param value */ private void updateCaches(String key, String value) { key = key.trim(); if (booleanCache.containsKey(key)) { booleanCache.put(key, new Boolean(value)); } colorScaleCache.remove(key); objectCache.remove(key); } private void clearCaches() { colorScaleCache.clear(); booleanCache.clear(); objectCache.clear(); } public void put(String key, String value) { key = key.trim(); // Explicitly setting removes override overrideKeys.remove(key); if (value == null || value.trim().length() == 0) { userPreferences.remove(key); } else { userPreferences.put(key, value); } updateCaches(key, value); IGVEventBus.getInstance().post(new PreferencesChangeEvent()); } public void put(String key, boolean b) { put(key, String.valueOf(b)); } public void putAll(Map<String, String> updatedPrefs) { for (Map.Entry<String, String> entry : updatedPrefs.entrySet()) { if (entry.getValue() == null || entry.getValue().trim().length() == 0) { remove(entry.getKey()); } else { put(entry.getKey(), entry.getValue()); } } clearCaches(); checkForAlignmentChanges(updatedPrefs); // TODO replace with event IGVEventBus.getInstance().post(new PreferencesChangeEvent()); } private void checkForAlignmentChanges(Map<String, String> updatedPreferenceMap) { if (IGV.hasInstance()) { final IGV igv = IGV.getInstance(); boolean reloadSAM = false; for (String key : SAM_RELOAD_KEYS) { if (updatedPreferenceMap.containsKey(key)) { reloadSAM = true; break; } } boolean refreshSAM = false; for (String key : SAM_REFRESH_KEYS) { if (updatedPreferenceMap.containsKey(key)) { refreshSAM = true; break; } } if (reloadSAM) { IGVEventBus.getInstance().post(new AlignmentTrackEvent(this, AlignmentTrackEvent.Type.RELOAD)); } // A reload is harsher than a refresh; only send the weaker request if the stronger one is not sent. if (!reloadSAM && refreshSAM) { IGVEventBus.getInstance().post(new AlignmentTrackEvent(this, AlignmentTrackEvent.Type.REFRESH)); } if (updatedPreferenceMap.containsKey(SAM_ALLELE_THRESHOLD)) { IGVEventBus.getInstance().post(new AlignmentTrackEvent(this, AlignmentTrackEvent.Type.ALLELE_THRESHOLD)); } } } public void remove(String key) { overrideKeys.remove(key); userPreferences.remove(key); booleanCache.remove(key); objectCache.remove(key); colorScaleCache.remove(key); IGVEventBus.getInstance().post(new PreferencesChangeEvent()); } public void clear() { userPreferences.clear(); colorScaleCache.clear(); booleanCache.clear(); objectCache.clear(); IGVEventBus.getInstance().post(new PreferencesChangeEvent()); } public String getGenomeListURL() { return get(GENOMES_SERVER_URL); } public void overrideGenomeServerURL(String url) { userPreferences.put(GENOMES_SERVER_URL, url); overrideKeys.add(GENOMES_SERVER_URL); clearCaches(); } /** * @param directory */ public void setLastExportedRegionDirectory(File directory) { put(LAST_EXPORTED_REGION_DIRECTORY, directory.getAbsolutePath()); } /** * @return */ public File getLastExportedRegionDirectory() { File exportedRegionDirectory = null; String lastFilePath = get(LAST_EXPORTED_REGION_DIRECTORY, null); if (lastFilePath != null) { // Create the exported region directory exportedRegionDirectory = new File(lastFilePath); } return exportedRegionDirectory; } /** * @param directory */ public void setLastSnapshotDirectory(File directory) { put(LAST_SNAPSHOT_DIRECTORY, directory.getAbsolutePath()); } /** * @return */ public File getLastSnapshotDirectory() { File snapshotDirectory = null; String lastFilePath = get(LAST_SNAPSHOT_DIRECTORY, null); if (lastFilePath != null) { // Create the snapshot directory snapshotDirectory = new File(lastFilePath); } return snapshotDirectory; } /** * @param directory */ public void setDefineGenomeInputDirectory(File directory) { put(DEFINE_GENOME_INPUT_DIRECTORY_KEY, directory.getAbsolutePath()); } /** * @return */ public File getDefineGenomeInputDirectory() { File directory = null; String lastFilePath = get(DEFINE_GENOME_INPUT_DIRECTORY_KEY, DirectoryManager.getUserDirectory().getAbsolutePath()); if (lastFilePath != null) { directory = new File(lastFilePath); } return directory; } /** * @param directory */ public void setLastGenomeImportDirectory(File directory) { put(LAST_GENOME_IMPORT_DIRECTORY, directory.getAbsolutePath()); } /** * @return */ public File getLastGenomeImportDirectory() { File genomeImportDirectory = null; String lastFilePath = get(LAST_GENOME_IMPORT_DIRECTORY, DirectoryManager.getUserDirectory().getAbsolutePath()); if (lastFilePath != null) { genomeImportDirectory = new File(lastFilePath); } return genomeImportDirectory; } /** * @param bounds */ public void setApplicationFrameBounds(Rectangle bounds) { if (bounds.width > 0 && bounds.height > 0) { StringBuffer buffer = new StringBuffer(); buffer.append(bounds.x); buffer.append(","); buffer.append(bounds.y); buffer.append(","); buffer.append(bounds.width); buffer.append(","); buffer.append(bounds.height); put(FRAME_BOUNDS_KEY, buffer.toString()); } } /** * @return */ public Rectangle getApplicationFrameBounds() { Rectangle bounds = null; // Set the application's previous location and size String applicationBounds = get(FRAME_BOUNDS_KEY, null); if (applicationBounds != null) { String[] values = applicationBounds.split(","); int x = Integer.parseInt(values[0]); int y = Integer.parseInt(values[1]); int width = Integer.parseInt(values[2]); int height = Integer.parseInt(values[3]); if (width == 0 || height == 0) { return null; // Don't know bounds } bounds = new Rectangle(x, y, width, height); } return bounds; } /** * @param recentSessions */ public void setRecentSessions(String recentSessions) { put(RECENT_SESSIONS, recentSessions); } public String getRecentSessions() { return get(RECENT_SESSIONS, null); } public String getDataServerURL() { String masterResourceFile = get(DATA_SERVER_URL_KEY); return masterResourceFile; } /** * Temporarily override the data server url with the supplied value. This override will persist for * the duration of the session, or until the user explicitly changes it. * * @param url */ public void overrideDataServerURL(String url) { userPreferences.put(DATA_SERVER_URL_KEY, url); overrideKeys.add(DATA_SERVER_URL_KEY); clearCaches(); } /** * Temporarily override a preference. This override will persist for * the duration of the session, or until the user explicitly changes it. * * @param key * @param value */ public void override(String key, String value) { userPreferences.put(key, value); overrideKeys.add(key); Map<String, String> updatedPrefs = new HashMap<String, String>(); updatedPrefs.put(key, value); checkForAlignmentChanges(updatedPrefs); clearCaches(); } public void setShowAttributeView(boolean isShowable) { put(SHOW_ATTRIBUTE_VIEWS_KEY, Boolean.toString(isShowable)); } public void setDefaultGenome(String genomeId) { if (!genomeId.equals(get(DEFAULT_GENOME))) { put(DEFAULT_GENOME, genomeId); } } public String getDefaultGenome() { String genome = get(DEFAULT_GENOME, Globals.DEFAULT_GENOME); return genome; } public void setLastTrackDirectory(File directory) { String lastDirectory = directory.isDirectory() ? directory.getAbsolutePath() : directory.getParent(); put(LAST_TRACK_DIRECTORY, lastDirectory); } public File getLastTrackDirectory() { String lastDirectoryPath = get(LAST_TRACK_DIRECTORY, null); File lastDirectoryFile = null; if (lastDirectoryPath != null) { lastDirectoryFile = new File(lastDirectoryPath); } return lastDirectoryFile; } /** * Set the color scheme for the track type. Its unfortunate that this is a public * method, color schemes are managed by ColorScaleFactory and that is the * only object that should call this method. * * @param type * @param colorScale */ public void setColorScale(TrackType type, ContinuousColorScale colorScale) { String colorScaleString = colorScale.asString(); put(COLOR_SCALE_KEY + type.toString(), colorScaleString); colorScaleCache.put(type, colorScale); } /** * Get the default color scheme for the track type. Only specific TrackTypes have default schemes. The scale * returned is marked as "default". * * @param type * @return */ public ContinuousColorScale getColorScale(TrackType type) { if (type == null) { return null; } ContinuousColorScale scale = colorScaleCache.get(type); if (scale == null && scaledTypes.contains(type)) { String colorScaleString = get(COLOR_SCALE_KEY + type.toString(), null); if (colorScaleString != null) { scale = (ContinuousColorScale) ColorScaleFactory.getScaleFromString(colorScaleString); } else { scale = getDefaultColorScale(type); } if (scale != null) { scale.setDefault(true); colorScaleCache.put(type, scale); } } return scale; } static Set<String> scaledTypes = new HashSet(Arrays.asList( TrackType.LOH, TrackType.RNAI, TrackType.POOLED_RNAI, TrackType.DNA_METHYLATION, TrackType.GENE_EXPRESSION, TrackType.COPY_NUMBER, TrackType.ALLELE_SPECIFIC_COPY_NUMBER, TrackType.CNV)); /** * Return the default color scale. This si the scale for track type "generic", * as well as any track type without a specific scale. * * @param type * @return */ public static ContinuousColorScale getDefaultColorScale(TrackType type) { switch (type) { case LOH: return new ContinuousColorScale(0, -1, 0, 1, Color.red, UIConstants.LIGHT_YELLOW, Color.blue); case RNAI: case POOLED_RNAI: ContinuousColorScale cs = new ContinuousColorScale(0, -3, 0, 3, Color.red, Color.white, Color.blue); cs.setNoDataColor(new Color(225, 225, 225)); return cs; case DNA_METHYLATION: cs = new ContinuousColorScale(0, 1, Color.BLUE, Color.RED); cs.setNoDataColor(Color.WHITE); return cs; case GENE_EXPRESSION: cs = getDefaultColorScale(Color.BLUE, Color.WHITE, Color.RED); cs.setNoDataColor(new Color(225, 225, 225)); return cs; case COPY_NUMBER: case ALLELE_SPECIFIC_COPY_NUMBER: case CNV: return getDefaultColorScale(Color.BLUE, Color.WHITE, Color.RED); default: return null; } } public static ContinuousColorScale getDefaultColorScale(Color negColor, Color neutralColor, Color posColor) { return new ContinuousColorScale(-0.1, -1.5, 0.1, 1.5, negColor, neutralColor, posColor); } /** * Original labels: Indel, Missense, Nonsesne, Splice_site, Synonymous, Targetd_Region, Unknown * Nico's labels: Synonymous, Missense, Truncating, Non-coding_Transcript, Other_AA_changing, Other_likely_neutral. */ public void resetMutationColorScheme() { remove(MUTATION_INDEL_COLOR_KEY); remove(MUTATION_MISSENSE_COLOR_KEY); remove(MUTATION_NONSENSE_COLOR_KEY); remove(MUTATION_SPLICE_SITE_COLOR_KEY); remove(MUTATION_SYNONYMOUS_COLOR_KEY); remove(MUTATION_TARGETED_REGION_COLOR_KEY); remove(MUTATION_UNKNOWN_COLOR_KEY); remove("MUTATION_Truncating_COLOR"); remove("MUTATION_Non-coding_Transcript_COLOR"); remove("MUTATION_Other_AA_changing_COLOR"); remove("MUTATION_Other_likely_neutral_COLOR"); remove(MUTATION_COLOR_TABLE); } /** * Original labels: Indel, Missense, Nonsesne, Splice_site, Synonymous, Targetd_Region, Unknown * Nico's labels: Synonymous, Missense, Truncating, Non-coding_Transcript, Other_AA_changing, Other_likely_neutral. * Combined: Indel, Missense, Nonsesne, Splice_site, Synonymous, Targetd_Region, Unknown, Truncating, * Non-coding_Transcript, Other_AA_changing, Other_likely_neutral */ public synchronized PaletteColorTable getMutationColorScheme() { if (mutationColorScheme == null) { String colorTableString = get(MUTATION_COLOR_TABLE); if (colorTableString != null) { PaletteColorTable pallete = new PaletteColorTable(); pallete.restoreMapFromString(colorTableString); mutationColorScheme = pallete; } else { mutationColorScheme = getLegacyMutationColorScheme(); } } return mutationColorScheme; } private PaletteColorTable getLegacyMutationColorScheme() { String indelColor = get(MUTATION_INDEL_COLOR_KEY); String missenseColor = get(MUTATION_MISSENSE_COLOR_KEY); String nonsenseColor = get(MUTATION_NONSENSE_COLOR_KEY); String spliceSiteColor = get(MUTATION_SPLICE_SITE_COLOR_KEY); String synonymousColor = get(MUTATION_SYNONYMOUS_COLOR_KEY); String targetedRegionColor = get(MUTATION_TARGETED_REGION_COLOR_KEY); String unknownColor = get(MUTATION_UNKNOWN_COLOR_KEY); PaletteColorTable colorTable = new PaletteColorTable(); if ((indelColor != null) && (missenseColor != null) && (nonsenseColor != null) && (spliceSiteColor != null) && (synonymousColor != null) && (targetedRegionColor != null) && (unknownColor != null)) { Color color1 = ColorUtilities.stringToColor(indelColor); colorTable.put("Indel", color1); Color color2 = ColorUtilities.stringToColor(missenseColor); colorTable.put("Missense", color2); Color color3 = ColorUtilities.stringToColor(nonsenseColor); colorTable.put("Nonsense", color3); Color color4 = ColorUtilities.stringToColor(spliceSiteColor); colorTable.put("Splice_site", color4); Color color5 = ColorUtilities.stringToColor(synonymousColor); colorTable.put("Synonymous", color5); Color color6 = ColorUtilities.stringToColor(targetedRegionColor); colorTable.put("Targeted_Region", color6); Color color7 = ColorUtilities.stringToColor(unknownColor); colorTable.put("Unknown", color7); // Nicos extensions String[] nicosCats = {"Truncating", "Non-coding_Transcript", "Other_AA_changing", "Other_likely_neutral"}; for (String cat : nicosCats) { String key = "MUTATION_" + cat + "_COLOR"; colorTable.put(cat, ColorUtilities.stringToColor(get(key))); } } return colorTable; } /** * Immediately clear all proxy settings. */ public void clearProxySettings() { remove(USE_PROXY); remove(PROXY_HOST); remove(PROXY_PORT); remove(PROXY_AUTHENTICATE); remove(PROXY_USER); remove(PROXY_PW); remove(PROXY_TYPE); remove(PROXY_WHITELIST); HttpUtils.getInstance().updateProxySettings(); } public static String generateGenomeIdString(Collection<GenomeListItem> genomeListItems) { String genomeString = ""; for (GenomeListItem serverItem : genomeListItems) { genomeString += serverItem.getId() + Globals.HISTORY_DELIMITER; } genomeString = genomeString.substring(0, genomeString.length() - 1); return genomeString; } /** * Get the path to the CLI plugin specified by the * Id and tool name. * * @param pluginId * @param toolName * @return * @see #putToolPath(String, String, String) * @see #genToolKey */ public String getToolPath(String pluginId, String toolName) { return get(genToolKey(pluginId, toolName, "path")); } /** * Set the path to the CLI plugin * * @param pluginId * @param toolName * @param path * @see #getToolPath(String, String) * @see #genToolKey */ public void putToolPath(String pluginId, String toolName, String path) { put(genToolKey(pluginId, toolName, "path"), path); } /** * Used to generate a unique string based on pluginId, toolName, and attribute * * @param pluginId * @param toolName * @param key * @return */ private String genToolKey(String pluginId, String toolName, String key) { return String.format("%s:%s:%s", pluginId, toolName.replace(' ', '_'), key.replace(' ', '_')); } public void putArgumentValue(String pluginId, String toolName, String command, String argName, String argValue) { String key = genArgKey(pluginId, toolName, command, argName); put(key, argValue); } public String getArgumentValue(String pluginId, String toolName, String commandName, String argId) { return get(genArgKey(pluginId, toolName, commandName, argId)); } private String genArgKey(String pluginId, String toolName, String command, String argId) { return genToolKey(pluginId, toolName, String.format("%s:%s", command, argId)); } public String[] getIGVPluginList() { return getAsArray(IGV_PLUGIN_LIST_KEY); } /** * Returns a preference value from either the preferences, * or system property. If the value was provided as a system property, * is is saved to the preferences. * Intended usecase is features only usable by certain groups but * not intended for all of IGV. * <p/> * Example * java -jar Denable.tools=true igv.jar * <p/> * getPersistent("enable.tools", null) returns "true" * and saves it to preferences, so a subsequent invocation * will return true as well: * java -jar igv.jar * getPersistent("enable.tools", "false") returns "true" also * * @param key * @param def default value. NOT SAVED * @return * @see org.broad.igv.session.Session#getPersistent(String, String) */ public String getPersistent(String key, String def) { String value = System.getProperty(key); if (value != null) { put(key, value); return value; } else { return get(key, def); } } public void print(PrintWriter pw) { for (Map.Entry<String, String> entry : userPreferences.entrySet()) { pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue()); } } /** * List of keys that affect the alignments loaded. This list is used to trigger a reload, if required. * Not all alignment preferences need trigger a reload, this is a subset. */ static java.util.List<String> SAM_RELOAD_KEYS = Arrays.asList( SAM_QUALITY_THRESHOLD, SAM_FILTER_ALIGNMENTS, SAM_FILTER_URL, SAM_MAX_VISIBLE_RANGE, SAM_SHOW_DUPLICATES, SAM_SHOW_SOFT_CLIPPED, SAM_SAMPLING_COUNT, SAM_SAMPLING_WINDOW, SAM_FILTER_FAILED_READS, SAM_DOWNSAMPLE_READS, SAM_FILTER_SECONDARY_ALIGNMENTS, SAM_FILTER_SUPPLEMENTARY_ALIGNMENTS, SAM_JUNCTION_MIN_FLANKING_WIDTH, SAM_JUNCTION_MIN_COVERAGE ); }