/*
* Autopsy Forensic Browser
*
* Copyright 2016 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.imagegallery.gui.navpanel;
import com.google.common.eventbus.Subscribe;
import java.util.Comparator;
import java.util.Optional;
import java.util.function.Function;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javax.swing.SortOrder;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.SortChooser;
/**
* Base class for Tabs in the left hand Navigation/Context area.
*/
abstract class NavPanel<X> extends Tab {
@FXML
private BorderPane borderPane;
@FXML
private ToolBar toolBar;
private final ImageGalleryController controller;
private final GroupManager groupManager;
private final CategoryManager categoryManager;
private SortChooser<DrawableGroup, GroupComparators<?>> sortChooser;
NavPanel(ImageGalleryController controller) {
this.controller = controller;
this.groupManager = controller.getGroupManager();
this.categoryManager = controller.getCategoryManager();
}
public ReadOnlyObjectProperty<GroupComparators<?>> comparatorProperty() {
return sortChooser.comparatorProperty();
}
@FXML
@NbBundle.Messages({"NavPanel.ascRadio.text=Ascending",
"NavPanel.descRadio.text=Descending",
"NavPanel.sortByBoxLabel.text=Sort By:"})
void initialize() {
assert borderPane != null : "fx:id=\"borderPane\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert toolBar != null : "fx:id=\"toolBar\" was not injected: check your FXML file 'NavPanel.fxml'.";
sortChooser = new SortChooser<>(GroupComparators.getValues());
sortChooser.setComparator(getDefaultComparator());
sortChooser.sortOrderProperty().addListener(order -> sortGroups());
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
sortGroups();
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count
if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) {
categoryManager.registerListener(NavPanel.this);
} else {
categoryManager.unregisterListener(NavPanel.this);
}
final SortChooser.ValueType valueType = newComparator == GroupComparators.ALPHABETICAL ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
sortChooser.setValueType(valueType);
});
toolBar.getItems().add(sortChooser);
//keep selection in sync with controller
controller.viewState().addListener(observable -> {
Optional.ofNullable(controller.viewState().get())
.map(GroupViewState::getGroup)
.ifPresent(this::setFocusedGroup);
});
getSelectionModel().selectedItemProperty().addListener(o -> updateControllersGroup());
}
/**
* @return the default comparator used by this "view" to sort groups
*/
abstract GroupComparators<?> getDefaultComparator();
@Subscribe
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
sortGroups();
}
/**
* @return the a comparator that will enforce the currently selected sorting
* options.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
Comparator<DrawableGroup> getComparator() {
Comparator<DrawableGroup> comparator = sortChooser.getComparator();
return (sortChooser.getSortOrder() == SortOrder.ASCENDING)
? comparator
: comparator.reversed();
}
/**
* notify controller about group selection in this view
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void updateControllersGroup() {
Optional.ofNullable(getSelectionModel().getSelectedItem())
.map(getDataItemMapper())
.ifPresent(group -> controller.advance(GroupViewState.tile(group), false));
}
/**
* Sort the groups in this view according to the currently selected sorting
* options. Attempts to maintain selection.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void sortGroups() {
X selectedItem = getSelectionModel().getSelectedItem();
applyGroupComparator();
Optional.ofNullable(selectedItem)
.map(getDataItemMapper())
.ifPresent(this::setFocusedGroup);
}
/**
* @return a function that maps the "native" data type of this view to a
* DrawableGroup
*/
abstract Function<X, DrawableGroup> getDataItemMapper();
/**
* Apply the currently selected sorting options.
*/
abstract void applyGroupComparator();
/**
*
* @return get the selection model
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
abstract SelectionModel<X> getSelectionModel();
/**
* attempt to set the given group as the selected/focused group in this
* view.
*
* @param grouping the grouping to attempt to select
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
abstract void setFocusedGroup(DrawableGroup grouping);
////boring getters
BorderPane getBorderPane() {
return borderPane;
}
ToolBar getToolBar() {
return toolBar;
}
ImageGalleryController getController() {
return controller;
}
GroupManager getGroupManager() {
return groupManager;
}
CategoryManager getCategoryManager() {
return categoryManager;
}
}