diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 30e0e6bd5f..ed3f3b307a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.TimeZone; import java.util.concurrent.ExecutionException; @@ -344,6 +345,9 @@ public class TimeLineController { filteredEvents.filterProperty().get(), DescriptionLoD.SHORT); historyManager.advance(InitialZoomState); + + //clear the selected events when the view mode changes + viewMode.addListener(observable -> selectEventIDs(Collections.emptySet())); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java index 52a0488d9f..71e022e65b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java @@ -35,6 +35,10 @@ public class CombinedEvent { private final long fileID; private final long epochMillis; private final String description; + + /** + * A map from EventType to event ID. + */ private final Map eventTypeMap = new HashMap<>(); /** @@ -43,7 +47,7 @@ public class CombinedEvent { * @param epochMillis The timestamp for this event, in millis from the Unix * epoch. * @param description The full description shared by all the combined events - * @param fileID The ID of the file all the combined events are for. + * @param fileID The ID of the file shared by all the combined events. * @param eventMap A map from EventType to event ID. */ public CombinedEvent(long epochMillis, String description, long fileID, Map eventMap) { @@ -145,5 +149,4 @@ public class CombinedEvent { } return true; } - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java index 38f05a9bfc..31d53a6b72 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java @@ -79,8 +79,8 @@ public abstract class AbstractTimelineChart menuItems = new ArrayList<>(); - //for each actions avaialable on node, make a menu item. - for (Action action : node.getActions(false)) { - if (action == null) { - // swing/netbeans uses null action to represent separator in menu - menuItems.add(new SeparatorMenuItem()); - } else { - String actionName = Objects.toString(action.getValue(Action.NAME)); - //for now, suppress properties and tools actions, by ignoring them - if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { - if (action instanceof Presenter.Popup) { - /* - * If the action is really the root of a set - * of actions (eg, tagging). Make a menu - * that parallels the action's menu. - */ - JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter(); - menuItems.add(SwingFXMenuUtils.createFXMenu(submenu)); - } else { - menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false))); + setOnContextMenuRequested(contextMenuEvent -> { + //make a new context menu on each request in order to include uptodate tag names and hash sets + try { + EventNode node = EventNode.createEventNode(item.getRepresentativeEventID(), controller.getEventsModel()); + List menuItems = new ArrayList<>(); + + //for each actions avaialable on node, make a menu item. + for (Action action : node.getActions(false)) { + if (action == null) { + // swing/netbeans uses null action to represent separator in menu + menuItems.add(new SeparatorMenuItem()); + } else { + String actionName = Objects.toString(action.getValue(Action.NAME)); + //for now, suppress properties and tools actions, by ignoring them + if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { + if (action instanceof Presenter.Popup) { + /* + * If the action is really the root of a + * set of actions (eg, tagging). Make a + * menu that parallels the action's + * menu. + */ + JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter(); + menuItems.add(SwingFXMenuUtils.createFXMenu(submenu)); + } else { + menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false))); + } } } - } - }; + }; + + //show new context menu. + new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()])) + .show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY()); + } catch (IllegalStateException ex) { + //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. + LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + Platform.runLater(() -> { + Notifications.create() + .owner(getScene().getWindow()) + .text(Bundle.ListChart_errorMsg()) + .showError(); + }); + } + }); - setContextMenu(new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()]))); - } catch (IllegalStateException ex) { - //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. - LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS - Platform.runLater(() -> { - Notifications.create() - .owner(getScene().getWindow()) - .text(Bundle.ListChart_errorMsg()) - .showError(); - }); - } } } }