Skip to content

Commit

Permalink
Add Recent Files Menu and Improve Recent Sessions (#1616)
Browse files Browse the repository at this point in the history
* Refactoring of the load files menu option

# Conflicts:
#	src/main/java/org/broad/igv/ui/action/LoadFromURLMenuAction.java

* Made the list of recent sessions update dynamically as sessions are create/loaded

* previously it only updated once when IGV was loaded

* Adding a "Recent Files" menu option

* This dynamically tracks the most recently loaded files/urls and allows reopening them
* Fixes ##1547

* Add comments and MenuSelectedListener

* Fix null check
  • Loading branch information
lbergelson authored Nov 19, 2024
1 parent 004130e commit 5a9d821
Show file tree
Hide file tree
Showing 23 changed files with 881 additions and 298 deletions.
1 change: 1 addition & 0 deletions src/main/java/org/broad/igv/prefs/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ private Constants() {

//
public static final String RECENT_SESSIONS = "IGV.Session.recent.sessions";
public static final String RECENT_URLS = "IGV.Session.recent.urls";
public static final String LAST_EXPORTED_REGION_DIRECTORY = "LAST_EXPORTED_REGION_DIRECTORY";
public static final String LAST_TRACK_DIRECTORY = "LAST_TRACK_DIRECTORY";
public static final String LAST_SNAPSHOT_DIRECTORY = "LAST_SNAPSHOT_DIRECTORY";
Expand Down
26 changes: 19 additions & 7 deletions src/main/java/org/broad/igv/prefs/IGVPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@
import org.broad.igv.renderer.SequenceRenderer;
import org.broad.igv.sam.mods.BaseModificationColors;
import org.broad.igv.track.TrackType;
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.IGVMenuBar;
import org.broad.igv.ui.UIConstants;
import org.broad.igv.ui.*;
import org.broad.igv.ui.color.ColorUtilities;
import org.broad.igv.ui.color.PaletteColorTable;
import org.broad.igv.ui.util.MessageUtils;
Expand Down Expand Up @@ -280,7 +278,7 @@ public void put(String key, String value) {
// Explicitly setting removes override
overrideKeys.remove(key);

if (value == null || value.trim().length() == 0) {
if (value == null || value.isBlank()) {
userPreferences.remove(key);
} else {
userPreferences.put(key, value);
Expand All @@ -297,7 +295,7 @@ public void put(String key, boolean 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) {
if (entry.getValue() == null || entry.getValue().isBlank()) {
remove(entry.getKey());
} else {
put(entry.getKey(), entry.getValue());
Expand Down Expand Up @@ -610,14 +608,28 @@ public Rectangle getApplicationFrameBounds() {
* @param recentSessions
*/
public void setRecentSessions(String recentSessions) {
remove(RECENT_SESSIONS);
put(RECENT_SESSIONS, recentSessions);
}


public String getRecentSessions() {
return get(RECENT_SESSIONS, null);
public RecentFileSet getRecentSessions() {
String sessionsString = get(RECENT_SESSIONS, null);
return RecentFileSet.fromString(sessionsString, UIConstants.NUMBER_OF_RECENT_SESSIONS_TO_LIST);
}

public void setRecentUrls(String recentUrls) {
remove(RECENT_URLS);
put(RECENT_URLS, recentUrls);
}


public RecentUrlsSet getRecentUrls() {
String sessionsString = get(RECENT_URLS, null);
return RecentUrlsSet.fromString(sessionsString, UIConstants.NUMBER_OF_RECENT_SESSIONS_TO_LIST);
}


public String getDataServerURL() {
String masterResourceFile = get(DATA_SERVER_URL_KEY);
return masterResourceFile;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.broad.igv.ui;

import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;

/**
* MenuListener which updates the availble menu items based on the contents of a changeable collection.
*
* Whenever the menu is selected the relevant JMenuItems are regenerated according to the current values in the backing
* collection.
*
* Items are inserted in a row beneath a given separator.
*
* @param <T> element type of the collection
*/
public class DynamicMenuItemsAdjustmentListener<T> implements MenuSelectedListener {
private final JMenu menu;

private final JSeparator insertionPoint;
private final Collection<T> values;
private final Function<T, JMenuItem> itemConstructor;

//Store the currently visible components here so they can be removed when necessary
private final List<JComponent> activeComponents;

/**
*
* @param menu the menu to modify
* @param insertionPoint a JSeparator which acts as an anchor to always insert elements below. This element is hidden
* when the collection is empty
* @param values a collection which is used to generate menu items
* @param itemConstructor a function to create a JMenuItem from an element in the collection
*/
public DynamicMenuItemsAdjustmentListener(JMenu menu, JSeparator insertionPoint, Collection<T> values, Function<T, JMenuItem> itemConstructor) {
this.menu = menu;
this.insertionPoint = insertionPoint;
this.values = values;
this.itemConstructor = itemConstructor;
this.activeComponents = new ArrayList<>();
}

private List<JMenuItem> getCurrentItems() {
return values.stream().map(itemConstructor).toList();
}

@Override
public void menuSelected(MenuEvent e) {
List<JMenuItem> newComponents = getCurrentItems();

// We definitely don't want to be doing this while other things are also changing the menu
// this should at least protect against multiple of these listeners modifying the same menu at once.
synchronized (menu) {
activeComponents.forEach(menu::remove);
if (newComponents.isEmpty()) {
insertionPoint.setVisible(false);
} else {
insertionPoint.setVisible(true);

final int componentIndex = Arrays.asList(menu.getMenuComponents()).indexOf(insertionPoint);
for (int i = 0; i < newComponents.size(); i++) {
menu.insert(newComponents.get(i), componentIndex + i + 1);
}
activeComponents.addAll(newComponents);
}
}

menu.revalidate();
menu.repaint();
}
}
77 changes: 47 additions & 30 deletions src/main/java/org/broad/igv/ui/IGV.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,10 @@ public class IGV implements IGVEventObserver {
private Timer sessionAutosaveTimer = new Timer();

// Misc state
private Map<String, List<Track>> overlayTracksMap = new HashMap();
private Set<Track> overlaidTracks = new HashSet();
private LinkedList<String> recentSessionList = new LinkedList<String>();
private Map<String, List<Track>> overlayTracksMap = new HashMap<>();
private Set<Track> overlaidTracks = new HashSet<>();
private RecentFileSet recentSessionList;
private RecentUrlsSet recentUrlsList;

// Vertical line that follows the mouse
private boolean rulerEnabled;
Expand Down Expand Up @@ -514,25 +515,15 @@ final public void doViewPreferences() {
final public void saveStateForExit() {

// Store recent sessions
if (!getRecentSessionList().isEmpty()) {

int size = getRecentSessionList().size();
if (size > UIConstants.NUMBER_OF_RECENT_SESSIONS_TO_LIST) {
size = UIConstants.NUMBER_OF_RECENT_SESSIONS_TO_LIST;
}

String recentSessions = "";
for (int i = 0; i <
size; i++) {
recentSessions += getRecentSessionList().get(i);

if (i < (size - 1)) {
recentSessions += ";";
}
RecentFileSet recentSessions = getRecentSessionList();
if (!recentSessions.isEmpty()) {
PreferencesManager.getPreferences().setRecentSessions(recentSessions.asString());
}

}
PreferencesManager.getPreferences().remove(RECENT_SESSIONS);
PreferencesManager.getPreferences().setRecentSessions(recentSessions);
// Store recent files
RecentUrlsSet recentUrls = getRecentUrls();
if (!recentUrls.isEmpty()) {
PreferencesManager.getPreferences().setRecentUrls(recentUrls.asString());
}

// Stop the timer that is triggering the timed autosave
Expand Down Expand Up @@ -991,9 +982,8 @@ public boolean loadSession(String sessionPath, String locus) {
}

mainFrame.setTitle(UIConstants.APPLICATION_NAME + " - Session: " + sessionPath);
if (!recentSessionList.contains(sessionPath)) {
recentSessionList.addFirst(sessionPath);
}

getRecentSessionList().add(sessionPath);
this.menuBar.enableReloadSession();

//If there's a RegionNavigatorDialog, kill it.
Expand All @@ -1006,7 +996,7 @@ public boolean loadSession(String sessionPath, String locus) {
} catch (Exception e) {
String message = "Error loading session session: " + e.getMessage();
MessageUtils.showMessage(message);
recentSessionList.remove(sessionPath);
getRecentSessionList().remove(sessionPath);
log.error(e);
return false;
} finally {
Expand Down Expand Up @@ -1068,9 +1058,8 @@ public void saveSession(File targetFile) throws IOException {
String sessionPath = targetFile.getAbsolutePath();
session.setPath(sessionPath);
mainFrame.setTitle(UIConstants.APPLICATION_NAME + " - Session: " + sessionPath);
if (!recentSessionList.contains(sessionPath)) {
recentSessionList.addFirst(sessionPath);
}

getRecentSessionList().add(sessionPath);
this.menuBar.enableReloadSession();

// No errors so save last location
Expand Down Expand Up @@ -1130,10 +1119,36 @@ public MainPanel getMainPanel() {
return contentPane.getMainPanel();
}

public LinkedList<String> getRecentSessionList() {
public RecentFileSet getRecentSessionList() {
if(recentSessionList == null){
recentSessionList = PreferencesManager.getPreferences().getRecentSessions();
//remove sessions that no longer exist
recentSessionList.removeIf(file -> !(new File(file)).exists());
}
return recentSessionList;
}

public RecentUrlsSet getRecentUrls() {
if(recentUrlsList == null){
recentUrlsList = PreferencesManager.getPreferences().getRecentUrls();
}
return recentUrlsList;
}

/**
* Add new values to the recent URLS set. Calling this method rather than adding them directly
* allows showing the menu when the first URL is added to the collection.
* @param toAdd
*/
public void addToRecentUrls(Collection<ResourceLocator> toAdd){
RecentUrlsSet recentFiles = getRecentUrls();
recentFiles.addAll(toAdd);
if(!recentFiles.isEmpty()){
menuBar.showRecentFilesMenu();
}
}


public IGVContentPane getContentPane() {
return contentPane;
}
Expand Down Expand Up @@ -1173,7 +1188,7 @@ public void loadResources(Collection<ResourceLocator> locators) {

for (final ResourceLocator locator : locators) {

// If its a local file, check explicitly for existence (rather than rely on exception)
// If it's a local file, check explicitly for existence (rather than rely on exception)
if (locator.isLocal()) {
File trackSetFile = new File(locator.getPath());
if (!trackSetFile.exists()) {
Expand All @@ -1182,9 +1197,11 @@ public void loadResources(Collection<ResourceLocator> locators) {
}
}


try {
List<Track> tracks = load(locator);
addTracks(tracks);

} catch (Exception e) {
log.error("Error loading track", e);
messages.append("Error loading " + locator + ": " + e.getMessage());
Expand Down
Loading

0 comments on commit 5a9d821

Please sign in to comment.