mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #1682 from millmanorama/TL-text-zoom-stack-overflow
further limit time zoom to events in case. limit time range selection…
This commit is contained in:
commit
55a6b18d4c
@ -567,7 +567,7 @@ public class TimeLineController {
|
||||
}
|
||||
|
||||
synchronized public void pushTimeRange(Interval timeRange) {
|
||||
// timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
|
||||
timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
|
||||
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
|
||||
if (currentZoom == null) {
|
||||
advance(InitialZoomState.withTimeRange(timeRange));
|
||||
|
@ -32,6 +32,9 @@ import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.util.Pair;
|
||||
@ -48,15 +51,19 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
*/
|
||||
public class SaveSnapshotAsReport extends Action {
|
||||
|
||||
private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16, true, true);
|
||||
private static final String HTML_EXT = ".html";
|
||||
private static final String REPORT_IMAGE_EXTENSION = ".png";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
|
||||
|
||||
@NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot",
|
||||
"SaveSnapshot.fileChoose.title.text=Save snapshot to"})
|
||||
public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) {
|
||||
@NbBundle.Messages({"SaveSnapshot.action.name.text=Snapshot",
|
||||
"SaveSnapshot.action.longText=Save a screen capture of the visualization as a report.",
|
||||
"SaveSnapshot.fileChoose.title.text=Save snapshot to",})
|
||||
public SaveSnapshotAsReport(TimeLineController controller, Node node) {
|
||||
super(Bundle.SaveSnapshot_action_name_text());
|
||||
setLongText(Bundle.SaveSnapshot_action_longText());
|
||||
setGraphic(new ImageView(SNAP_SHOT));
|
||||
setEventHandler(new Consumer<ActionEvent>() {
|
||||
|
||||
@Override
|
||||
@ -86,6 +93,7 @@ public class SaveSnapshotAsReport extends Action {
|
||||
|
||||
//save snapshot as png
|
||||
try {
|
||||
WritableImage snapshot = node.snapshot(null, null);
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png",
|
||||
new File(reportPath, reportName + REPORT_IMAGE_EXTENSION)); // NON-NLS
|
||||
} catch (IOException ex) {
|
||||
|
@ -31,7 +31,7 @@ public class ZoomIn extends Action {
|
||||
|
||||
private static final Image MAGNIFIER_IN = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-in-green.png"); //NOI18N
|
||||
|
||||
@NbBundle.Messages({"ZoomIn.longText=Zoom in to view half as much time.",
|
||||
@NbBundle.Messages({"ZoomIn.longText=Zoom in to view about half as much time.",
|
||||
"ZoomIn.action.text=Zoom in"})
|
||||
public ZoomIn(TimeLineController controller) {
|
||||
super(Bundle.ZoomIn_action_text());
|
||||
|
@ -33,7 +33,7 @@ public class ZoomOut extends Action {
|
||||
|
||||
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N
|
||||
|
||||
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view 50% more time.",
|
||||
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view about 50% more time.",
|
||||
"ZoomOut.action.text=Zoom out"})
|
||||
public ZoomOut(TimeLineController controller) {
|
||||
super(Bundle.ZoomOut_action_text());
|
||||
|
@ -41,18 +41,11 @@ VisualizationPanel.histogramTask.preparing=preparing
|
||||
VisualizationPanel.histogramTask.resetUI=resetting ui
|
||||
VisualizationPanel.histogramTask.queryDb=querying db
|
||||
VisualizationPanel.histogramTask.updateUI2=updating ui
|
||||
VisualizationPanel.visualizationModeLabel.text=Visualization Mode\:
|
||||
StatusBar.refreshLabel.text=New events may be available, re-open the timeline to refresh.
|
||||
StatusBar.messageLabel.text=Label
|
||||
StatusBar.taskLabel.text=Label
|
||||
VisualizationPanel.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.
|
||||
VisualizationPanel.zoomButton.text=Zoom to events
|
||||
VisualizationPanel.startLabel.text=Start\:
|
||||
VisualizationPanel.endLabel.text=End\:
|
||||
TimeZonePanel.localRadio.text=Local Time Zone
|
||||
TimeZonePanel.otherRadio.text=GMT / UTC
|
||||
VisualizationPanel.zoomMenuButton.text=Zoom in/out to
|
||||
VisualizationPanel.snapShotButton.text=Screenshot
|
||||
VisualizationPanel.detailsToggle.text=Details
|
||||
VisualizationPanel.countsToggle.text=Counts
|
||||
VisualizationPanel.resetFiltersButton.text=Reset all filters
|
@ -19,12 +19,13 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
@ -36,8 +37,6 @@ import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
@ -58,7 +57,10 @@ import javafx.scene.layout.Region;
|
||||
import static javafx.scene.layout.Region.USE_PREF_SIZE;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.util.Callback;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import jfxtras.scene.control.LocalDateTimePicker;
|
||||
import jfxtras.scene.control.LocalDateTimeTextField;
|
||||
import org.controlsfx.control.NotificationPane;
|
||||
import org.controlsfx.control.RangeSlider;
|
||||
@ -81,8 +83,6 @@ import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_refresh;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
|
||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
@ -91,29 +91,34 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
/**
|
||||
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to
|
||||
* hold settings widgets supplied by contained {@link AbstAbstractVisualization}
|
||||
* and the histogram / timeselection on bottom. Also supplies containers for
|
||||
* and, the histogram / time selection on bottom. Also supplies containers for
|
||||
* replacement axis to contained {@link AbstractAbstractVisualization}
|
||||
*
|
||||
* TODO: refactor common code out of histogram and CountsView? -jm
|
||||
*/
|
||||
final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
|
||||
|
||||
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
||||
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
|
||||
private static final Background background = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
|
||||
|
||||
@GuardedBy("this")
|
||||
private LoggedTask<Void> histogramTask;
|
||||
|
||||
private final EventsTree eventsTree;
|
||||
|
||||
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
|
||||
|
||||
//// range slider and histogram componenets
|
||||
/**
|
||||
* hbox that contains the histogram bars. //TODO: abstract this into a
|
||||
* seperate class, and/or use a real bar chart?
|
||||
*/
|
||||
@FXML
|
||||
private HBox histogramBox;
|
||||
|
||||
/**
|
||||
* stack pane that superimposes rangeslider over histogram
|
||||
*/
|
||||
@FXML
|
||||
private StackPane rangeHistogramStack;
|
||||
|
||||
@ -123,97 +128,128 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@FXML
|
||||
private MenuButton zoomMenuButton;
|
||||
|
||||
@FXML
|
||||
private Separator rightSeperator;
|
||||
|
||||
@FXML
|
||||
private Separator leftSeperator;
|
||||
|
||||
@FXML
|
||||
private Button zoomOutButton;
|
||||
|
||||
@FXML
|
||||
private Button zoomInButton;
|
||||
|
||||
@FXML
|
||||
private LocalDateTimeTextField startPicker;
|
||||
|
||||
@FXML
|
||||
private LocalDateTimeTextField endPicker;
|
||||
@FXML
|
||||
private Label startLabel;
|
||||
@FXML
|
||||
private Label endLabel;
|
||||
|
||||
//// replacemetn axis label componenets
|
||||
@FXML
|
||||
private Pane partPane;
|
||||
|
||||
@FXML
|
||||
private Pane contextPane;
|
||||
|
||||
@FXML
|
||||
private Region spacer;
|
||||
|
||||
//// header toolbar componenets
|
||||
@FXML
|
||||
private ToolBar toolBar;
|
||||
|
||||
@FXML
|
||||
private ToggleButton countsToggle;
|
||||
|
||||
@FXML
|
||||
private ToggleButton detailsToggle;
|
||||
|
||||
@FXML
|
||||
private Button snapShotButton;
|
||||
@FXML
|
||||
private Label visualizationModeLabel;
|
||||
@FXML
|
||||
private Label startLabel;
|
||||
|
||||
@FXML
|
||||
private Label endLabel;
|
||||
|
||||
/**
|
||||
* wraps contained visualization so that we can show notifications over it.
|
||||
*/
|
||||
private final NotificationPane notificationPane = new NotificationPane();
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
|
||||
private final ChangeListener<Object> rangeSliderListener =
|
||||
(observable1, oldValue, newValue) -> {
|
||||
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
|
||||
/**
|
||||
* listen to change in range slider selected time and push to controller.
|
||||
* waits until the user releases thumb to send controller.
|
||||
*/
|
||||
private final InvalidationListener rangeSliderListener = new InvalidationListener() {
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
if (rangeSlider.isHighValueChanging() == false
|
||||
&& rangeSlider.isLowValueChanging() == false) {
|
||||
Long minTime = filteredEvents.getMinTime() * 1000;
|
||||
controller.pushTimeRange(new Interval(
|
||||
new Double(rangeSlider.getLowValue() + minTime).longValue(),
|
||||
new Double(rangeSlider.getHighValue() + minTime).longValue(),
|
||||
DateTimeZone.UTC));
|
||||
}
|
||||
};
|
||||
|
||||
private final InvalidationListener endListener = (Observable observable) -> {
|
||||
if (endPicker.getLocalDateTime() != null) {
|
||||
controller.pushTimeRange(VisualizationPanel.this.filteredEvents.timeRangeProperty().get().withEndMillis(
|
||||
ZonedDateTime.of(endPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
|
||||
}
|
||||
};
|
||||
|
||||
private final InvalidationListener startListener = (Observable observable) -> {
|
||||
if (startPicker.getLocalDateTime() != null) {
|
||||
controller.pushTimeRange(VisualizationPanel.this.filteredEvents.timeRangeProperty().get().withStartMillis(
|
||||
ZonedDateTime.of(startPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
|
||||
/**
|
||||
* listens to timerange of filtered events model and updates UI to match
|
||||
*/
|
||||
final private InvalidationListener timeRangeInvalidationListener = new InvalidationListener() {
|
||||
public void invalidated(Observable observable) {
|
||||
refreshTimeUI(filteredEvents.timeRangeProperty().get());
|
||||
}
|
||||
};
|
||||
|
||||
static private final Background background = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
|
||||
/**
|
||||
* hides the notification pane on any event
|
||||
*/
|
||||
private final InvalidationListener zoomListener = any -> notificationPane.hide();
|
||||
|
||||
static private final Lighting lighting = new Lighting();
|
||||
/**
|
||||
* listen to change in end time picker and push to controller
|
||||
*/
|
||||
private final InvalidationListener endListener = new PickerListener(() -> endPicker, Interval::withEndMillis);
|
||||
|
||||
public VisualizationPanel(TimeLineController controller, EventsTree eventsTree) {
|
||||
/**
|
||||
* listen to change in start time picker and push to controller
|
||||
*/
|
||||
private final InvalidationListener startListener = new PickerListener(() -> startPicker, Interval::withStartMillis);
|
||||
|
||||
/**
|
||||
* convert the given LocalDateTime to epoch millis USING THE CURERNT
|
||||
* TIMEZONE FROM TIMELINECONTROLLER
|
||||
*
|
||||
* @param localDateTime
|
||||
*
|
||||
* @return the given localdatetime as epoch millis
|
||||
*/
|
||||
private static long localDateTimeToEpochMilli(LocalDateTime localDateTime) {
|
||||
return localDateTime.atZone(TimeLineController.getTimeZoneID()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given epoch millis to a LocalDateTime USING THE CURERNT
|
||||
* TIMEZONE FROM TIMELINECONTROLLER
|
||||
*
|
||||
* @param millis
|
||||
*
|
||||
* @return the given epoch millis as a LocalDateTime
|
||||
*/
|
||||
private static LocalDateTime epochMillisToLocalDateTime(long millis) {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), TimeLineController.getTimeZoneID());
|
||||
}
|
||||
|
||||
public VisualizationPanel(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.eventsTree = eventsTree;
|
||||
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@FXML // This method is called by the FXMLLoader when initialization is complete
|
||||
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
|
||||
@NbBundle.Messages({"VisualizationPanel.refresh=refresh",
|
||||
"VisualizationPanel.visualizationModeLabel.text=Visualization Mode:",
|
||||
"VisualizationPanel.startLabel.text=Start:",
|
||||
"VisualizationPanel.endLabel.text=End:",
|
||||
"VisualizationPanel.countsToggle.text=Counts",
|
||||
"VisualizationPanel.detailsToggle.text=Details",
|
||||
"VisualizationPanel.zoomMenuButton.text=Zoom in/out to"})
|
||||
void initialize() {
|
||||
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
@ -222,8 +258,9 @@ final public class VisualizationPanel extends BorderPane {
|
||||
assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
|
||||
assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
|
||||
|
||||
//configure notification pane
|
||||
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
|
||||
notificationPane.getActions().setAll(new Action(VisualizationPanel_refresh()) {
|
||||
notificationPane.getActions().setAll(new Action(Bundle.VisualizationPanel_refresh()) {
|
||||
{
|
||||
setGraphic(new ImageView(REFRESH));
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
@ -233,15 +270,12 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
});
|
||||
setCenter(notificationPane);
|
||||
visualizationModeLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.visualizationModeLabel.text")); // NON-NLS
|
||||
startLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.startLabel.text")); // NON-NLS
|
||||
endLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.endLabel.text")); // NON-NLS
|
||||
|
||||
HBox.setHgrow(leftSeperator, Priority.ALWAYS);
|
||||
HBox.setHgrow(rightSeperator, Priority.ALWAYS);
|
||||
ChangeListener<Toggle> toggleListener = (ObservableValue<? extends Toggle> observable,
|
||||
Toggle oldValue,
|
||||
Toggle newValue) -> {
|
||||
//configure visualization mode toggle
|
||||
visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text());
|
||||
countsToggle.setText(Bundle.VisualizationPanel_countsToggle_text());
|
||||
detailsToggle.setText(Bundle.VisualizationPanel_detailsToggle_text());
|
||||
ChangeListener<Toggle> toggleListener = (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> {
|
||||
if (newValue == null) {
|
||||
countsToggle.getToggleGroup().selectToggle(oldValue != null ? oldValue : countsToggle);
|
||||
} else if (newValue == countsToggle && oldValue != null) {
|
||||
@ -258,14 +292,34 @@ final public class VisualizationPanel extends BorderPane {
|
||||
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
|
||||
});
|
||||
}
|
||||
countsToggle.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.countsToggle.text")); // NON-NLS
|
||||
detailsToggle.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.detailsToggle.text")); // NON-NLS
|
||||
controller.viewModeProperty().addListener(observable -> setViewMode(controller.viewModeProperty().get()));
|
||||
setViewMode(controller.viewModeProperty().get());
|
||||
|
||||
//configure snapshor button / action
|
||||
ActionUtils.configureButton(new SaveSnapshotAsReport(controller, VisualizationPanel.this), snapShotButton);
|
||||
|
||||
/////configure start and end pickers
|
||||
startLabel.setText(Bundle.VisualizationPanel_startLabel_text());
|
||||
endLabel.setText(Bundle.VisualizationPanel_endLabel_text());
|
||||
|
||||
//suppress stacktraces on malformed input
|
||||
//TODO: should we do anything else? show a warning?
|
||||
startPicker.setParseErrorCallback(throwable -> null);
|
||||
endPicker.setParseErrorCallback(throwable -> null);
|
||||
|
||||
//disable dates outside scope of case
|
||||
LocalDateDisabler localDateDisabler = new LocalDateDisabler();
|
||||
startPicker.setLocalDateTimeRangeCallback(localDateDisabler);
|
||||
endPicker.setLocalDateTimeRangeCallback(localDateDisabler);
|
||||
|
||||
//prevent selection of (date/)times outside the scope of this case
|
||||
startPicker.setValueValidationCallback(new LocalDateTimeValidator(startPicker));
|
||||
endPicker.setValueValidationCallback(new LocalDateTimeValidator(endPicker));
|
||||
|
||||
//setup rangeslider
|
||||
rangeSlider.setOpacity(.7);
|
||||
rangeSlider.setMin(0);
|
||||
rangeSlider.setBlockIncrement(1);
|
||||
|
||||
rangeHistogramStack.getChildren().add(rangeSlider);
|
||||
|
||||
/*
|
||||
@ -275,56 +329,39 @@ final public class VisualizationPanel extends BorderPane {
|
||||
*/
|
||||
histogramBox.setStyle(" -fx-padding: 0,0.5em,0,.5em; "); // NON-NLS
|
||||
|
||||
//configure zoom buttons
|
||||
zoomMenuButton.getItems().clear();
|
||||
for (ZoomRanges b : ZoomRanges.values()) {
|
||||
MenuItem menuItem = new MenuItem(b.getDisplayName());
|
||||
menuItem.setOnAction((event) -> {
|
||||
if (b != ZoomRanges.ALL) {
|
||||
controller.pushPeriod(b.getPeriod());
|
||||
for (ZoomRanges zoomRange : ZoomRanges.values()) {
|
||||
zoomMenuButton.getItems().add(ActionUtils.createMenuItem(
|
||||
new Action(zoomRange.getDisplayName(), event -> {
|
||||
if (zoomRange != ZoomRanges.ALL) {
|
||||
controller.pushPeriod(zoomRange.getPeriod());
|
||||
} else {
|
||||
controller.showFullRange();
|
||||
}
|
||||
});
|
||||
zoomMenuButton.getItems().add(menuItem);
|
||||
})));
|
||||
}
|
||||
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
|
||||
|
||||
snapShotButton.setOnAction(event ->
|
||||
this.snapshot(snapShotResult -> {
|
||||
new SaveSnapshotAsReport(controller, snapShotResult.getImage()).handle(event);
|
||||
return null;
|
||||
}, null, null)
|
||||
);
|
||||
|
||||
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
|
||||
if (this.filteredEvents != null && this.filteredEvents != controller.getEventsModel()) {
|
||||
this.filteredEvents.unRegisterForEvents(this);
|
||||
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
||||
}
|
||||
if (this.filteredEvents != controller.getEventsModel()) {
|
||||
controller.getEventsModel().registerForEvents(this);
|
||||
controller.getEventsModel().timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||
controller.getEventsModel().zoomParametersProperty().addListener(zoomListener);
|
||||
}
|
||||
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
refreshTimeUI(controller.getEventsModel().timeRangeProperty().get());
|
||||
zoomMenuButton.setText(Bundle.VisualizationPanel_zoomMenuButton_text());
|
||||
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
|
||||
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
|
||||
|
||||
setViewMode(controller.viewModeProperty().get());
|
||||
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||
//register for EventBus events (tags)
|
||||
filteredEvents.registerForEvents(this);
|
||||
|
||||
//listen for changes in the time range / zoom params
|
||||
TimeLineController.getTimeZone().addListener(timeRangeInvalidationListener);
|
||||
filteredEvents.timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||
filteredEvents.zoomParametersProperty().addListener(zoomListener);
|
||||
refreshTimeUI(filteredEvents.timeRangeProperty().get()); //populate the viz
|
||||
|
||||
//this should use an event(EventBus) , not this weird observable pattern
|
||||
controller.getNeedsHistogramRebuild().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
refreshHistorgram();
|
||||
}
|
||||
});
|
||||
|
||||
controller.viewModeProperty().addListener((ObservableValue<? extends VisualizationMode> ov, VisualizationMode t, VisualizationMode t1) -> {
|
||||
setViewMode(t1);
|
||||
});
|
||||
TimeLineController.getTimeZone().addListener(timeRangeInvalidationListener);
|
||||
refreshHistorgram();
|
||||
|
||||
}
|
||||
|
||||
private void setViewMode(VisualizationMode visualizationMode) {
|
||||
@ -382,19 +419,19 @@ final public class VisualizationPanel extends BorderPane {
|
||||
TagsFilter tagsFilter = filteredEvents.getFilter().getTagsFilter();
|
||||
if (tagsFilter.isSelected() && tagsFilter.isDisabled() == false) {
|
||||
Platform.runLater(() -> {
|
||||
notificationPane.show(VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
|
||||
notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private void refreshHistorgram() {
|
||||
|
||||
if (histogramTask != null) {
|
||||
histogramTask.cancel(true);
|
||||
}
|
||||
|
||||
histogramTask = new LoggedTask<Void>(
|
||||
NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS
|
||||
private final Lighting lighting = new Lighting();
|
||||
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
@ -471,18 +508,10 @@ final public class VisualizationPanel extends BorderPane {
|
||||
controller.monitorTask(histogramTask);
|
||||
}
|
||||
|
||||
private InvalidationListener timeRangeInvalidationListener = (Observable observable) -> {
|
||||
refreshTimeUI(filteredEvents.timeRangeProperty().get());
|
||||
};
|
||||
|
||||
private InvalidationListener zoomListener = (Observable observable) -> {
|
||||
notificationPane.hide();
|
||||
};
|
||||
|
||||
private void refreshTimeUI(Interval interval) {
|
||||
RangeDivisionInfo rangeDivisionInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
|
||||
|
||||
final Long minTime = rangeDivisionInfo.getLowerBound();
|
||||
final long minTime = rangeDivisionInfo.getLowerBound();
|
||||
final long maxTime = rangeDivisionInfo.getUpperBound();
|
||||
|
||||
if (minTime > 0 && maxTime > minTime) {
|
||||
@ -493,11 +522,13 @@ final public class VisualizationPanel extends BorderPane {
|
||||
rangeSlider.highValueChangingProperty().removeListener(rangeSliderListener);
|
||||
rangeSlider.lowValueChangingProperty().removeListener(rangeSliderListener);
|
||||
|
||||
rangeSlider.setMax((Long) (maxTime - minTime));
|
||||
rangeSlider.setHighValue(interval.getEndMillis() - minTime);
|
||||
rangeSlider.setLowValue(interval.getStartMillis() - minTime);
|
||||
endPicker.setLocalDateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(interval.getEndMillis()), TimeLineController.getTimeZoneID()));
|
||||
startPicker.setLocalDateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(interval.getStartMillis()), TimeLineController.getTimeZoneID()));
|
||||
rangeSlider.setMax((maxTime - minTime));
|
||||
long startMillis = interval.getStartMillis();
|
||||
long endMillis = interval.getEndMillis();
|
||||
rangeSlider.setLowValue(startMillis - minTime);
|
||||
rangeSlider.setHighValue(endMillis - minTime);
|
||||
startPicker.setLocalDateTime(epochMillisToLocalDateTime(startMillis));
|
||||
endPicker.setLocalDateTime(epochMillisToLocalDateTime(endMillis));
|
||||
|
||||
rangeSlider.highValueChangingProperty().addListener(rangeSliderListener);
|
||||
rangeSlider.lowValueChangingProperty().addListener(rangeSliderListener);
|
||||
@ -509,26 +540,17 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
private class NoEventsDialog extends TitledPane {
|
||||
|
||||
private final Runnable closeCallback;
|
||||
|
||||
@FXML // ResourceBundle that was given to the FXMLLoader
|
||||
private ResourceBundle resources;
|
||||
|
||||
@FXML // URL location of the FXML file that was given to the FXMLLoader
|
||||
private URL location;
|
||||
|
||||
@FXML
|
||||
private Button resetFiltersButton;
|
||||
|
||||
@FXML
|
||||
private Button dismissButton;
|
||||
|
||||
@FXML
|
||||
private Button zoomButton;
|
||||
|
||||
@FXML
|
||||
private Label noEventsDialogLabel;
|
||||
|
||||
private final Runnable closeCallback;
|
||||
|
||||
private NoEventsDialog(Runnable closeCallback) {
|
||||
this.closeCallback = closeCallback;
|
||||
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
|
||||
@ -550,4 +572,90 @@ final public class VisualizationPanel extends BorderPane {
|
||||
resetFiltersButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.resetFiltersButton.text")); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for listeners that listen to a LocalDateTimeTextField and push
|
||||
* the selected LocalDateTime as start/end to the timelinecontroller.
|
||||
*/
|
||||
private class PickerListener implements InvalidationListener {
|
||||
|
||||
private final BiFunction< Interval, Long, Interval> intervalMapper;
|
||||
private final Supplier<LocalDateTimeTextField> pickerSupplier;
|
||||
|
||||
PickerListener(Supplier<LocalDateTimeTextField> pickerSupplier, BiFunction<Interval, Long, Interval> intervalMapper) {
|
||||
this.pickerSupplier = pickerSupplier;
|
||||
this.intervalMapper = intervalMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
|
||||
if (pickerTime != null) {
|
||||
controller.pushTimeRange(intervalMapper.apply(filteredEvents.timeRangeProperty().get(), localDateTimeToEpochMilli(pickerTime)));
|
||||
Platform.runLater(() -> refreshTimeUI(filteredEvents.timeRangeProperty().get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* callback that disabled date/times outside the span of the current case.
|
||||
*/
|
||||
private class LocalDateDisabler implements Callback<LocalDateTimePicker.LocalDateTimeRange, Void> {
|
||||
|
||||
@Override
|
||||
public Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange) {
|
||||
startPicker.disabledLocalDateTimes().clear();
|
||||
endPicker.disabledLocalDateTimes().clear();
|
||||
|
||||
//all events in the case are contained in this interval
|
||||
Interval spanningInterval = filteredEvents.getSpanningInterval();
|
||||
long spanStartMillis = spanningInterval.getStartMillis();
|
||||
long spaneEndMillis = spanningInterval.getEndMillis();
|
||||
|
||||
LocalDate rangeStartLocalDate = viewedRange.getStartLocalDateTime().toLocalDate();
|
||||
LocalDate rangeEndLocalDate = viewedRange.getEndLocalDateTime().toLocalDate().plusDays(1);
|
||||
//iterate over days of the displayed range and disable ones not in spanning interval
|
||||
for (LocalDate dt = rangeStartLocalDate; false == dt.isAfter(rangeEndLocalDate); dt = dt.plusDays(1)) {
|
||||
long startOfDay = dt.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
|
||||
long endOfDay = dt.plusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
|
||||
//if no part of day is within spanning interval, add that date the list of disabled dates.
|
||||
if (endOfDay < spanStartMillis || startOfDay > spaneEndMillis) {
|
||||
startPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
|
||||
endPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that validates that selected date/times are in the spanning
|
||||
* interval for this case, and resets the textbox if invalid date/time was
|
||||
* entered.
|
||||
*/
|
||||
private class LocalDateTimeValidator implements Callback<LocalDateTime, Boolean> {
|
||||
|
||||
/**
|
||||
* picker to reset if invalid info was entered
|
||||
*/
|
||||
private final LocalDateTimeTextField picker;
|
||||
|
||||
LocalDateTimeValidator(LocalDateTimeTextField picker) {
|
||||
this.picker = picker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean call(LocalDateTime param) {
|
||||
long epochMilli = localDateTimeToEpochMilli(param);
|
||||
if (filteredEvents.getSpanningInterval().contains(epochMilli)) {
|
||||
return true;
|
||||
} else {
|
||||
if (picker.isPickerShowing() == false) {
|
||||
//if the user typed an in valid date, reset the text box to the selected date.
|
||||
picker.setDisplayedLocalDateTime(picker.getLocalDateTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user