Skip to content

Commit 342111e

Browse files
committed
PropertyManager listens for PropertyUpdatedEvent. Fixes #401
1 parent 96dce96 commit 342111e

File tree

3 files changed

+76
-43
lines changed

3 files changed

+76
-43
lines changed

EnrichmentMapPlugin/src/main/java/org/baderlab/csplugins/enrichmentmap/CyActivator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.cytoscape.application.swing.CyColumnPresentation;
3333
import org.cytoscape.command.StringTunableHandlerFactory;
3434
import org.cytoscape.property.CyProperty;
35+
import org.cytoscape.property.PropertyUpdatedListener;
3536
import org.cytoscape.service.util.AbstractCyActivator;
3637
import org.cytoscape.view.presentation.customgraphics.CyCustomGraphics2Factory;
3738
import org.cytoscape.work.ServiceProperties;
@@ -78,6 +79,8 @@ public void start(BundleContext bc) {
7879
// CyProperty
7980
CyProperty<Properties> cyProperty = injector.getInstance(Key.get(new TypeLiteral<CyProperty<Properties>>(){}));
8081
registerAllServices(bc, cyProperty, PropsReader.getServiceProps());
82+
PropertyManager propertyManager = injector.getInstance(PropertyManager.class);
83+
registerService(bc, propertyManager, PropertyUpdatedListener.class);
8184

8285
boolean headless = injector.getInstance(Key.get(Boolean.class, Headless.class));
8386

EnrichmentMapPlugin/src/main/java/org/baderlab/csplugins/enrichmentmap/PropertyManager.java

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,24 @@
1515
import org.baderlab.csplugins.enrichmentmap.view.creation.genemania.StringDialogParameters;
1616
import org.baderlab.csplugins.enrichmentmap.view.heatmap.HeatMapParams.Distance;
1717
import org.cytoscape.property.CyProperty;
18+
import org.cytoscape.property.PropertyUpdatedEvent;
19+
import org.cytoscape.property.PropertyUpdatedListener;
1820

1921
import com.google.inject.Inject;
2022
import com.google.inject.Singleton;
2123

2224
/**
2325
* Manages the CyProperties for EnrichmentMap.
26+
* The CyProperty API is very limited, and it doesn't fire events when you programmatically
27+
* update a property. This manager provides a nicer interface for managing properties and it
28+
* actually fires events when you expect it to.
2429
*/
2530
@Singleton
26-
public class PropertyManager {
31+
public class PropertyManager implements PropertyUpdatedListener {
2732

28-
@FunctionalInterface
29-
public interface PropertyListener<T> {
30-
void propertyChanged(Property<T> prop, T value);
31-
}
32-
33-
private final Map<Property<?>,List<PropertyListener<?>>> listeners = new HashMap<>();
33+
@Inject private CyProperty<Properties> emCyProps;
3434

35+
// To add a new property just add a declaration below, reflection is used to read these static fields...
3536

3637
public static final Property<Boolean> HEATMAP_AUTOFOCUS = Property.of("heatmapAutofocus", false);
3738
public static final Property<Boolean> HEATMAP_DATASET_SYNC = Property.of("heatmapDatasetSync", true);
@@ -55,37 +56,57 @@ public interface PropertyListener<T> {
5556
public static final Property<String> GENEMANIA_COLUMN_ANN_NAME = Property.of("genemania.column.annname", GenemaniaDialogParameters.ANNOTATION_NAME_COLUMN_DEF);
5657

5758

59+
@FunctionalInterface
60+
public interface PropertyListener<T> {
61+
void propertyChanged(Property<T> prop, T value);
62+
}
63+
64+
private final Map<Property<?>,List<PropertyListener<?>>> listeners = new HashMap<>();
5865

59-
@Inject private CyProperty<Properties> cyProps;
6066

6167
@AfterInjection
6268
private void initializeProperties() {
6369
getAllProperties()
6470
.stream()
65-
.filter(prop -> !cyProps.getProperties().containsKey(prop.key))
71+
.filter(prop -> !emCyProps.getProperties().containsKey(prop.key))
6672
.forEach(this::setDefault);
6773
}
6874

75+
76+
@SuppressWarnings({ "unchecked", "rawtypes" })
77+
@Override
78+
public void handleEvent(PropertyUpdatedEvent e) {
79+
// This only fires when the user updates a property using the Edit > Preferences > Properties dialog.
80+
// The API for this doesn't give enough information, we don't know which property actually changed,
81+
// so need to update all of them.
82+
if(e.getSource() == emCyProps) {
83+
for(Property p : getAllProperties()) {
84+
fire(p, getValue(p));
85+
}
86+
}
87+
}
88+
6989
public <T> void addListener(Property<T> property, PropertyListener<T> listener) {
7090
listeners.computeIfAbsent(property, k -> new ArrayList<>()).add(listener);
7191
}
7292

93+
public <T> void removeListener(Property<T> property, PropertyListener<T> listener) {
94+
listeners.get(property).remove(listener);
95+
}
96+
7397
public <T> void setValue(Property<T> property, T value) {
74-
cyProps.getProperties().setProperty(property.key, String.valueOf(value));
75-
76-
for(PropertyListener<?> listener : listeners.getOrDefault(property, Collections.emptyList())) {
77-
((PropertyListener<T>)listener).propertyChanged(property, value);
78-
}
98+
emCyProps.getProperties().setProperty(property.key, String.valueOf(value));
99+
fire(property, value);
79100
}
80101

81102
public <T> void setDefault(Property<T> property) {
82103
setValue(property, property.def);
83104
}
84105

85106
public <T> T getValue(Property<T> property) {
86-
if(cyProps == null) // happens in JUnits
107+
if(emCyProps == null) // happens in JUnits
87108
return property.def;
88-
Properties properties = cyProps.getProperties();
109+
Properties properties = emCyProps.getProperties();
89110
if(properties == null)
90111
return property.def;
91112
String string = properties.getProperty(property.key);
@@ -119,6 +140,13 @@ public static List<Property<?>> getAllProperties() {
119140
}
120141

121142

143+
@SuppressWarnings("unchecked")
144+
private <T> void fire(Property<T> property, T value) {
145+
for(PropertyListener<?> listener : listeners.getOrDefault(property, Collections.emptyList())) {
146+
((PropertyListener<T>)listener).propertyChanged(property, value);
147+
}
148+
}
149+
122150
public static class Property<T> {
123151
private final String key;
124152
public final T def;
@@ -143,4 +171,5 @@ public static Property<Double> of(String key, double defaultValue) {
143171
return new Property<>(key, defaultValue, Double::valueOf);
144172
}
145173
}
174+
146175
}

EnrichmentMapPlugin/src/main/java/org/baderlab/csplugins/enrichmentmap/view/heatmap/OptionsPopup.java

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import org.baderlab.csplugins.enrichmentmap.AfterInjection;
1616
import org.baderlab.csplugins.enrichmentmap.PropertyManager;
17+
import org.baderlab.csplugins.enrichmentmap.PropertyManager.Property;
18+
import org.baderlab.csplugins.enrichmentmap.PropertyManager.PropertyListener;
1719
import org.baderlab.csplugins.enrichmentmap.view.heatmap.HeatMapParams.Distance;
1820
import org.baderlab.csplugins.enrichmentmap.view.util.IconUtil;
1921
import org.cytoscape.util.swing.IconManager;
@@ -88,30 +90,11 @@ private void createContents() {
8890
pearsonRadio = new JCheckBoxMenuItem("Pearson Correlation");
8991
pearsonRadio.addActionListener(pearsonListener = distanceListenerFor(Distance.PEARSON));
9092
distanceMenu.add(pearsonRadio);
91-
92-
JCheckBoxMenuItem autofocusCheckbox = new JCheckBoxMenuItem("Auto-Focus HeatMap");
93-
autofocusCheckbox.setSelected(propertyManager.getValue(PropertyManager.HEATMAP_AUTOFOCUS));
94-
autofocusCheckbox.addActionListener(e -> {
95-
propertyManager.setValue(PropertyManager.HEATMAP_AUTOFOCUS, autofocusCheckbox.isSelected());
96-
});
97-
98-
JCheckBoxMenuItem syncCheckbox = new JCheckBoxMenuItem("Sync Data Sets with Control Panel");
99-
syncCheckbox.setSelected(propertyManager.getValue(PropertyManager.HEATMAP_DATASET_SYNC));
100-
syncCheckbox.addActionListener(e -> {
101-
propertyManager.setValue(PropertyManager.HEATMAP_DATASET_SYNC, syncCheckbox.isSelected());
102-
});
10393

104-
JCheckBoxMenuItem selectedCheckbox = new JCheckBoxMenuItem("Display only selected Data Sets");
105-
selectedCheckbox.setSelected(propertyManager.getValue(PropertyManager.HEATMAP_SELECT_SYNC));
106-
selectedCheckbox.addActionListener(e -> {
107-
propertyManager.setValue(PropertyManager.HEATMAP_SELECT_SYNC, selectedCheckbox.isSelected());
108-
});
109-
110-
JCheckBoxMenuItem autoSortCheckbox = new JCheckBoxMenuItem("Auto sort leading edge");
111-
autoSortCheckbox.setSelected(propertyManager.getValue(PropertyManager.HEATMAP_AUTO_SORT));
112-
autoSortCheckbox.addActionListener(e -> {
113-
propertyManager.setValue(PropertyManager.HEATMAP_AUTO_SORT, autoSortCheckbox.isSelected());
114-
});
94+
JCheckBoxMenuItem autofocusCheck = createPropItem(PropertyManager.HEATMAP_AUTOFOCUS, "Auto-Focus HeatMap");
95+
JCheckBoxMenuItem syncCheck = createPropItem(PropertyManager.HEATMAP_DATASET_SYNC, "Sync Data Sets with Control Panel");
96+
JCheckBoxMenuItem selectedCheck = createPropItem(PropertyManager.HEATMAP_SELECT_SYNC, "Display only selected Data Sets");
97+
JCheckBoxMenuItem autoSortCheck = createPropItem(PropertyManager.HEATMAP_AUTO_SORT, "Auto sort leading edge");
11598

11699
add(geneManiaButton);
117100
add(stringButton);
@@ -122,12 +105,30 @@ private void createContents() {
122105
add(exportPdfButton);
123106
addSeparator();
124107
add(distanceMenu);
125-
add(autofocusCheckbox);
126-
add(syncCheckbox);
127-
add(selectedCheckbox);
128-
add(autoSortCheckbox);
108+
add(autofocusCheck);
109+
add(syncCheck);
110+
add(selectedCheck);
111+
add(autoSortCheck);
112+
}
113+
114+
115+
private JCheckBoxMenuItem createPropItem(Property<Boolean> property, String label) {
116+
JCheckBoxMenuItem checkbox = new JCheckBoxMenuItem(label);
117+
checkbox.setSelected(propertyManager.isTrue(property));
118+
119+
PropertyListener<Boolean> listener = (prop, value) -> checkbox.setSelected(value);
120+
propertyManager.addListener(property, listener);
121+
122+
checkbox.addActionListener(e -> {
123+
propertyManager.removeListener(property, listener);
124+
propertyManager.setValue(property, checkbox.isSelected());
125+
propertyManager.addListener(property, listener);
126+
});
127+
128+
return checkbox;
129129
}
130130

131+
131132
private <T> ActionListener distanceListenerFor(Distance dm) {
132133
return e -> {
133134
if(distanceConsumer != null) {

0 commit comments

Comments
 (0)