diff --git a/src/org/openlcb/cdi/swing/CdiPanel.java b/src/org/openlcb/cdi/swing/CdiPanel.java index 54202327..bb933ab4 100644 --- a/src/org/openlcb/cdi/swing/CdiPanel.java +++ b/src/org/openlcb/cdi/swing/CdiPanel.java @@ -2447,7 +2447,7 @@ void updateColor() { if (eventTableEntryHolder.getEntry().getEvent().equals(id)) { return; } - releaseListener(); + eraseListener(); } if (id == null || id.equals(nullEvent)) { // Ignore event if it is the null event. @@ -2465,8 +2465,14 @@ private void releaseListener() { eventTableEntryHolder.release(); eventTableEntryHolder = null; } - } + private void eraseListener() { + if (eventTableEntryHolder == null) return; + eventTableEntryHolder.getList().removePropertyChangeListener(eventListUpdateListener); + eventTableEntryHolder.erase(); + eventTableEntryHolder = null; + } + } // represent a slider with an optional text view diff --git a/src/org/openlcb/implementations/EventTable.java b/src/org/openlcb/implementations/EventTable.java index d6e8c26c..e6f550a9 100644 --- a/src/org/openlcb/implementations/EventTable.java +++ b/src/org/openlcb/implementations/EventTable.java @@ -23,13 +23,33 @@ */ @ThreadSafe -public class EventTable { +public class EventTable extends DefaultPropertyListenerSupport { private final HashMap entries = new HashMap<>(); /// This property change notification is produced when the list of descriptions registered /// for a given event ID has changed (due to addition, removal or description change). public final static String UPDATED_EVENT_LIST = "UPDATED_EVENT_LIST"; + /// This property change notification is produced in addition to UPDATED_EVENT_LIST + /// when a given eventID is removed. + public final static String DESCRIPTION_ADDED = "DESCRIPTION_ADDED"; + + /// This property change notification is produced in addition to UPDATED_EVENT_LIST + /// when a given eventID holder is removed, e.g. when closing a CDI window. + public final static String DESCRIPTION_REMOVED = "DESCRIPTION_REMOVED"; + + /// This property change notification is produced in addition to UPDATED_EVENT_LIST + /// when a given eventID description has been erased, e.g. the event ID description is permanently changed + public final static String DESCRIPTION_ERASED = "DESCRIPTION_ERASED"; + + /// This property change notification is produced in addition to UPDATED_EVENT_LIST + /// when a given eventID is removed. + public final static String DESCRIPTION_UPDATED = "DESCRIPTION_UPDATED"; + + /// This property change notification is produced when a new EventID-description + /// pair is added via the addEvent method + public final static String EVENT_ENTRY_ADDED = "EVENT_ENTRY_ADDED"; + /** * Looks up a given event ID and tells what we know about it. * @@ -59,7 +79,19 @@ EventInfo getEventInfo(EventID event) { * keep this and call the release method before going out of scope. */ public EventTableEntryHolder addEvent(EventID event, String description) { - return getEventInfo(event).add(description); + synchronized (entries) { + EventTableEntryHolder holder = getEventInfo(event).add(description); + notifyUpdated(holder); // notify _after_ the entry is added + return holder; + } + } + + + /** + * Helper function used by the modifying functions. + */ + void notifyUpdated(EventTableEntryHolder holder) { + firePropertyChange(EVENT_ENTRY_ADDED, null, holder); } /** @@ -230,13 +262,15 @@ public EventTableEntryHolder add(String description) { synchronized (entries) { entries.add(newEntry); } - notifyUpdated(); + notifyEventUpdated(DESCRIPTION_ADDED); return h; } /** - * Removes the entry represented by a given holder object. This method is not public, - * please use Holder.release() as the client API. + * Removes the entry represented by a given holder object. + * To be used e.g, when a CDI window is closed to drop the connection to the + * event description, without implying that the description has been erased. + * This method is not public, please use Holder.release() as the client API. * * @param h the holder object. */ @@ -249,14 +283,37 @@ void remove(EventTableEntryHolder h) { } } } - notifyUpdated(); + notifyEventUpdated(DESCRIPTION_REMOVED); + } + + /** + * Removes the entry represented by a given holder object. + * To be used e.g, when an event ID in an input field has changed, implying + * that the associated description is no longer relevant for the event ID. + * This method is not public, please use Holder.release() as the client API. + * + * @param h the holder object. + */ + void erase(EventTableEntryHolder h) { + synchronized (entries) { + for (int i = 0; i < entries.size(); ++i) { + if (entries.get(i).h == h) { + entries.remove(i); + --i; + } + } + } + notifyEventUpdated(DESCRIPTION_ERASED); } /** * Helper function used by the modifying functions. */ - void notifyUpdated() { + void notifyEventUpdated(String reason) { + // fired first for every notification firePropertyChange(UPDATED_EVENT_LIST, null, this); + // then fire the specific reason + firePropertyChange(reason, null, this); } /** @@ -320,7 +377,7 @@ public void updateDescription(String newDescription) { if (description.equals(newDescription)) return; description = newDescription; } - h.event.notifyUpdated(); + h.event.notifyEventUpdated(DESCRIPTION_UPDATED); } } @@ -340,11 +397,21 @@ public class EventTableEntryHolder { /** * Removes the pointed entry from the event table. + * This is used for transitent removals, e.g. closing a CDI window. */ public void release() { event.remove(this); } + /** + * Removes the pointed entry from the event table. + * This is used for permenent removal of a description, + * e.g. while editing an EventID field + */ + public void erase() { + event.erase(this); + } + /** * @return the pointed event table entry. */ diff --git a/test/org/openlcb/implementations/EventTableTest.java b/test/org/openlcb/implementations/EventTableTest.java index 7c01adb1..b66e1e7a 100644 --- a/test/org/openlcb/implementations/EventTableTest.java +++ b/test/org/openlcb/implementations/EventTableTest.java @@ -63,27 +63,29 @@ public void testAddRemove() { } class FakeListener implements PropertyChangeListener { - EventTable.EventInfo newValue = null; + int calls = 0; + String expectedCall; @Override public void propertyChange(PropertyChangeEvent propertyChangeEvent) { - Assert.assertEquals(EventTable.UPDATED_EVENT_LIST, propertyChangeEvent.getPropertyName()); + if (! propertyChangeEvent.getPropertyName().equals(expectedCall) && + ! propertyChangeEvent.getPropertyName().equals(EventTable.UPDATED_EVENT_LIST)) + Assert.fail(propertyChangeEvent.getPropertyName()+" unexpected"); + + Assert.assertNull(propertyChangeEvent.getOldValue()); - - Assert.assertNull("duplicate listener call", newValue); - newValue = (EventTable.EventInfo) propertyChangeEvent.getNewValue(); - Assert.assertNotNull(newValue); + + calls = calls+1; } - public void reset() { newValue = null; } + public void reset() { calls = 0; } public void verifyCall(EventTable.EventInfo expected) { - Assert.assertNotNull("Expected call, but did not happen.", newValue); - Assert.assertTrue("Incorrect newValue passed to property listener", newValue == expected); + Assert.assertEquals("Expected call, but did not happen.", 2, calls); reset(); } public void verifyNoInteraction() { - Assert.assertNull("Expected no call, got one.", newValue); + Assert.assertTrue("Expected no call, got one.", calls == 0); } } @@ -94,18 +96,23 @@ public void testNotify() { elist.addPropertyChangeListener(l); l.verifyNoInteraction(); + + l.expectedCall = EventTable.DESCRIPTION_ADDED; EventTable.EventTableEntryHolder h1 = elist.add("teste3"); l.verifyCall(elist); elist.add("teste3alt"); l.verifyCall(elist); + l.expectedCall = EventTable.DESCRIPTION_REMOVED; h1.release(); l.verifyCall(elist); + l.expectedCall = EventTable.DESCRIPTION_ADDED; h1 = elist.add("testf3"); l.verifyCall(elist); + l.expectedCall = EventTable.DESCRIPTION_UPDATED; h1.getEntry().updateDescription("testf3bar"); l.verifyCall(elist); }