package org.jabref.gui;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.SwingUtilities;
import org.jabref.Globals;
import org.jabref.gui.maintable.MainTable;
import org.jabref.preferences.JabRefPreferences;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Manages visibility of SideShowComponents in a given newly constructed
* sidePane.
*/
public class SidePaneManager {
private static final Log LOGGER = LogFactory.getLog(SidePaneManager.class);
private final JabRefFrame frame;
private final SidePane sidep;
private final Map<Class<? extends SidePaneComponent>, SidePaneComponent> components = new LinkedHashMap<>();
private final List<SidePaneComponent> visible = new LinkedList<>();
public SidePaneManager(JabRefFrame frame) {
this.frame = frame;
/*
* Change by Morten Alver 2005.12.04: By postponing the updating of the
* side pane components, we get rid of the annoying latency when
* switching tabs:
*/
frame.getTabbedPane().addChangeListener(event -> SwingUtilities.invokeLater(
() -> setActiveBasePanel(SidePaneManager.this.frame.getCurrentBasePanel())));
sidep = new SidePane();
sidep.setVisible(false);
}
public SidePane getPanel() {
return sidep;
}
public synchronized <T extends SidePaneComponent> boolean hasComponent(Class<T> sidePaneComponent) {
return components.containsKey(sidePaneComponent);
}
public synchronized <T extends SidePaneComponent> boolean isComponentVisible(Class<T> sidePaneComponent) {
SidePaneComponent component = components.get(sidePaneComponent);
if (component == null) {
return false;
} else {
return visible.contains(component);
}
}
/**
* If panel is visible it will be hidden and the other way around
*/
public synchronized <T extends SidePaneComponent> void toggle(Class<T> sidePaneComponent) {
if (isComponentVisible(sidePaneComponent)) {
hide(sidePaneComponent);
} else {
show(sidePaneComponent);
}
}
/**
* If panel is hidden it will be shown and focused
* If panel is visible but not focused it will be focused
* If panel is visible and focused it will be hidden
*/
public synchronized <T extends SidePaneComponent> void toggleThreeWay(Class<T> sidePaneComponent) {
boolean isPanelFocused = Globals.getFocusListener().getFocused() == components.get(sidePaneComponent);
if (isComponentVisible(sidePaneComponent) && isPanelFocused) {
hide(sidePaneComponent);
} else {
show(sidePaneComponent);
}
}
public synchronized <T extends SidePaneComponent> void show(Class<T> sidePaneComponent) {
SidePaneComponent component = components.get(sidePaneComponent);
if (component == null) {
LOGGER.warn("Side pane component '" + sidePaneComponent + "' unknown.");
} else {
show(component);
}
}
public synchronized <T extends SidePaneComponent> void hide(Class<T> sidePaneComponent) {
SidePaneComponent component = components.get(sidePaneComponent);
if (component == null) {
LOGGER.warn("Side pane component '" + sidePaneComponent + "' unknown.");
} else {
hideComponent(component);
if (frame.getCurrentBasePanel() != null) {
MainTable mainTable = frame.getCurrentBasePanel().getMainTable();
mainTable.setSelected(mainTable.getSelectedRow());
mainTable.requestFocus();
}
}
}
public synchronized void register(SidePaneComponent comp) {
components.put(comp.getClass(), comp);
}
private synchronized void show(SidePaneComponent component) {
if (!visible.contains(component)) {
// Put the new component at the top of the group
visible.add(0, component);
// Sort the visible components by their preferred position
Collections.sort(visible, new PreferredIndexSort());
updateView();
component.componentOpening();
}
Globals.getFocusListener().setFocused(component);
component.grabFocus();
}
public synchronized <T extends SidePaneComponent> SidePaneComponent getComponent(Class<T> sidePaneComponent) {
return components.get(sidePaneComponent);
}
public synchronized void hideComponent(SidePaneComponent comp) {
if (visible.contains(comp)) {
comp.componentClosing();
visible.remove(comp);
updateView();
}
}
public synchronized <T extends SidePaneComponent> void hideComponent(Class<T> sidePaneComponent) {
SidePaneComponent component = components.get(sidePaneComponent);
if (component == null) {
return;
}
if (visible.contains(component)) {
component.componentClosing();
visible.remove(component);
updateView();
}
}
private static Map<Class<? extends SidePaneComponent>, Integer> getPreferredPositions() {
Map<Class<? extends SidePaneComponent>, Integer> preferredPositions = new HashMap<>();
List<String> componentNames = Globals.prefs.getStringList(JabRefPreferences.SIDE_PANE_COMPONENT_NAMES);
List<String> componentPositions = Globals.prefs
.getStringList(JabRefPreferences.SIDE_PANE_COMPONENT_PREFERRED_POSITIONS);
for (int i = 0; i < componentNames.size(); ++i) {
String componentName = componentNames.get(i);
try {
Class<? extends SidePaneComponent> componentClass = (Class<? extends SidePaneComponent>) Class.forName(componentName);
preferredPositions.put(componentClass, Integer.parseInt(componentPositions.get(i)));
} catch (ClassNotFoundException e) {
LOGGER.debug("Following side pane could not be found: " + componentName, e);
} catch (ClassCastException e) {
LOGGER.debug("Following Class is no side pane: '" + componentName, e);
} catch (NumberFormatException e) {
LOGGER.debug("Invalid number format for side pane component '" + componentName + "'.", e);
}
}
return preferredPositions;
}
private void updatePreferredPositions() {
Map<Class<? extends SidePaneComponent>, Integer> preferredPositions = getPreferredPositions();
// Update the preferred positions of all visible components
int index = 0;
for (SidePaneComponent comp : visible) {
preferredPositions.put(comp.getClass(), index);
index++;
}
// Split the map into a pair of parallel String lists suitable for storage
List<String> tmpComponentNames = preferredPositions.keySet().parallelStream()
.map(Class::getName)
.collect(Collectors.toList());
List<String> componentPositions = preferredPositions.values().stream().map(Object::toString)
.collect(Collectors.toList());
Globals.prefs.putStringList(JabRefPreferences.SIDE_PANE_COMPONENT_NAMES, tmpComponentNames);
Globals.prefs.putStringList(JabRefPreferences.SIDE_PANE_COMPONENT_PREFERRED_POSITIONS, componentPositions);
}
/**
* Helper class for sorting visible components based on their preferred position
*/
private class PreferredIndexSort implements Comparator<SidePaneComponent> {
private final Map<Class<? extends SidePaneComponent>, Integer> preferredPositions;
public PreferredIndexSort() {
preferredPositions = getPreferredPositions();
}
@Override
public int compare(SidePaneComponent comp1, SidePaneComponent comp2) {
int pos1 = preferredPositions.getOrDefault(comp1.getClass(), 0);
int pos2 = preferredPositions.getOrDefault(comp2.getClass(), 0);
return Integer.valueOf(pos1).compareTo(pos2);
}
}
public synchronized void moveUp(SidePaneComponent comp) {
if (visible.contains(comp)) {
int currIndex = visible.indexOf(comp);
if (currIndex > 0) {
int newIndex = currIndex - 1;
visible.remove(currIndex);
visible.add(newIndex, comp);
updatePreferredPositions();
updateView();
}
}
}
public synchronized void moveDown(SidePaneComponent comp) {
if (visible.contains(comp)) {
int currIndex = visible.indexOf(comp);
if (currIndex < (visible.size() - 1)) {
int newIndex = currIndex + 1;
visible.remove(currIndex);
visible.add(newIndex, comp);
updatePreferredPositions();
updateView();
}
}
}
public synchronized <T extends SidePaneComponent> void unregisterComponent(Class<T> sidePaneComponent) {
components.remove(sidePaneComponent);
}
/**
* Update all side pane components to show information from the given
* BasePanel.
*
* @param panel
*/
private synchronized void setActiveBasePanel(BasePanel panel) {
for (SidePaneComponent component : components.values()) {
component.setActiveBasePanel(panel);
}
}
public synchronized void updateView() {
sidep.setComponents(visible);
if (visible.isEmpty()) {
if (sidep.isVisible()) {
Globals.prefs.putInt(JabRefPreferences.SIDE_PANE_WIDTH, frame.getSplitPane().getDividerLocation());
}
sidep.setVisible(false);
} else {
boolean wasVisible = sidep.isVisible();
sidep.setVisible(true);
if (!wasVisible) {
int width = Globals.prefs.getInt(JabRefPreferences.SIDE_PANE_WIDTH);
if (width > 0) {
frame.getSplitPane().setDividerLocation(width);
} else {
frame.getSplitPane().setDividerLocation(getPanel().getPreferredSize().width);
}
}
}
}
}