/* * Autopsy Forensic Browser * * Copyright 2013-16 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.timeline.zooming; import java.util.function.Consumer; import java.util.function.Function; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.Slider; import javafx.scene.control.TitledPane; import javafx.util.StringConverter; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.ViewMode; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; /** * A Panel that acts as a view for a given * TimeLineController/FilteredEventsModel. It has sliders to provide * context/control over three axes of zooming (timescale, event hierarchy level, * and description level of detail). */ public class ZoomSettingsPane extends TitledPane { @FXML private Label zoomLabel; @FXML private Label descrLODLabel; @FXML private Slider descrLODSlider; @FXML private Label typeZoomLabel; @FXML private Slider typeZoomSlider; @FXML private Label timeUnitLabel; @FXML private Slider timeUnitSlider; private final TimeLineController controller; private final FilteredEventsModel filteredEvents; /** * Constructor * * @param controller TimeLineController this panel functions as a view for. */ public ZoomSettingsPane(TimeLineController controller) { this.controller = controller; this.filteredEvents = controller.getEventsModel(); FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS } @NbBundle.Messages({ "ZoomSettingsPane.descrLODLabel.text=Description Detail:", "ZoomSettingsPane.typeZoomLabel.text=Event Type:", "ZoomSettingsPane.timeUnitLabel.text=Time Units:", "ZoomSettingsPane.zoomLabel.text=Zoom"}) public void initialize() { zoomLabel.setText(Bundle.ZoomSettingsPane_zoomLabel_text()); typeZoomSlider.setMin(1); //don't show ROOT_TYPE typeZoomSlider.setMax(EventTypeZoomLevel.values().length - 1); configureSliderListeners(typeZoomSlider, controller::pushEventTypeZoom, filteredEvents.eventTypeZoomProperty(), EventTypeZoomLevel.class, EventTypeZoomLevel::ordinal, Function.identity()); typeZoomLabel.setText(Bundle.ZoomSettingsPane_typeZoomLabel_text()); descrLODSlider.setMax(DescriptionLoD.values().length - 1); configureSliderListeners(descrLODSlider, controller::pushDescrLOD, filteredEvents.descriptionLODProperty(), DescriptionLoD.class, DescriptionLoD::ordinal, Function.identity()); descrLODLabel.setText(Bundle.ZoomSettingsPane_descrLODLabel_text()); //the description slider is only usefull in the detail view descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(ViewMode.COUNTS)); /** * In order for the selected value in the time unit slider to correspond * to the amount of time used as units along the x-axis of the view, and * since we don't want to show "forever" as a time unit, the range of * the slider is restricted, and there is an offset of 1 between the * "real" value, and what is shown in the slider labels. */ timeUnitSlider.setMax(TimeUnits.values().length - 2); configureSliderListeners(timeUnitSlider, controller::pushTimeUnit, filteredEvents.timeRangeProperty(), TimeUnits.class, //for the purposes of this slider we want the TimeUnit one bigger than RangeDivisionInfo indicates modelTimeRange -> RangeDivisionInfo.getRangeDivisionInfo(modelTimeRange).getPeriodSize().ordinal() - 1, index -> index + 1); //compensate for the -1 above when mapping to the Enum whose displayName will be shown at index timeUnitLabel.setText(Bundle.ZoomSettingsPane_timeUnitLabel_text()); //hide the whole panel in list mode BooleanBinding notListMode = controller.viewModeProperty().isNotEqualTo(ViewMode.LIST); visibleProperty().bind(notListMode); managedProperty().bind(notListMode); } /** * Configure the listeners that keep the given slider in sync with model * property changes, and that handle user input on the slider. The listener * attached to the slider is added and removed to avoid circular updates. * * Because Sliders work in terms of Doubles but represent ordered Enums that * are indexed by Integers, and because the model properties may not be of * the same type as the Enum(timeUnitSlider relates to an Interval in the * filteredEvents model, rather than the TimeUnits shown on the Slider), a * mapper is needed to convert between DriverType and Integer * indices(driverValueMapper). Another mapper is used to modifiy the mapping * from Integer index to Enum value displayed as the slider tick * label(labelIndexMapper). * * @param slider The slider that we are configuring. * * @param sliderValueConsumer The consumer that will get passed the newly * selected slider value (mapped to EnumType * automatically). * * @param modelProperty The readonly model property that this slider * should be synced to. * * @param enumClass A type token for EnumType, ie value of type * Class<EnumType> * * @param driverValueMapper A Function that maps from driver values of * type DriverType to Integers representing the * index of the corresponding EnumType. * * @param labelIndexMapper A Function that maps from Integer (narrowed * slider value) to Integers representing the * index of the corresponding EnumType. Used to * compensate for slider values that do not * lineup exactly with the Enum value indices to * use as tick Labels. */ private static <DriverType, EnumType extends Enum<EnumType> & DisplayNameProvider> void configureSliderListeners( Slider slider, Consumer<EnumType> sliderValueConsumer, ReadOnlyObjectProperty<DriverType> modelProperty, Class<EnumType> enumClass, Function<DriverType, Integer> driverValueMapper, Function<Integer, Integer> labelIndexMapper) { //set the tick labels to the enum displayNames slider.setLabelFormatter(new EnumSliderLabelFormatter<>(enumClass, labelIndexMapper)); //make a listener to responds to slider value changes (by updating the view) final InvalidationListener sliderListener = observable -> { //only process event if the slider value is not changing (user has released slider thumb) if (slider.isValueChanging() == false) { //convert slider value to EnumType and pass to consumer EnumType sliderValueAsEnum = enumClass.getEnumConstants()[Math.round((float) slider.getValue())]; sliderValueConsumer.accept(sliderValueAsEnum); } }; //attach listener slider.valueProperty().addListener(sliderListener); slider.valueChangingProperty().addListener(sliderListener); //set intial value of slider slider.setValue(driverValueMapper.apply(modelProperty.get())); //handle changes in the model property modelProperty.addListener(modelProp -> { //remove listener to avoid circular updates slider.valueProperty().removeListener(sliderListener); slider.valueChangingProperty().removeListener(sliderListener); Platform.runLater(() -> { //sync value of slider to model property value slider.setValue(driverValueMapper.apply(modelProperty.get())); //reattach listener slider.valueProperty().addListener(sliderListener); slider.valueChangingProperty().addListener(sliderListener); }); }); } /** * StringConverter for the tick Labels of a Slider that is "backed" by an * Enum that extends DisplayNameProvider. Narrows the Slider's Double value * to an Integer and then uses that as the index of the Enum value whose * displayName will be shown as the tick Label * * @param <EnumType> The type of Enum that this converter works with. */ static private class EnumSliderLabelFormatter<EnumType extends Enum<EnumType> & DisplayNameProvider> extends StringConverter<Double> { /** * A Type token for the class of Enum that this converter works with. */ private final Class<EnumType> clazz; /** * * A Function that can be used to adjust the narrowed slider value if it * doesn't correspond exactly to the Enum value index. */ private final Function<Integer, Integer> indexAdjsuter; EnumSliderLabelFormatter(Class<EnumType> clazz, Function<Integer, Integer> indexMapper) { this.clazz = clazz; this.indexAdjsuter = indexMapper; } @Override public String toString(Double dbl) { //get the displayName of the EnumType whose index is the given dbl after it has been narrowed and then adjusted return clazz.getEnumConstants()[indexAdjsuter.apply(dbl.intValue())].getDisplayName(); } @Override public Double fromString(String string) { throw new UnsupportedOperationException("This method should not be used. This EnumSliderLabelFormatter is being used in an unintended way."); } } }