/*
* Autopsy Forensic Browser
*
* Copyright 2013-14 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;
import com.google.common.collect.ImmutableSet;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.logging.Level;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javax.swing.SwingUtilities;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Manages set of selected fileIds, as well as last selected fileID. Since some
* actions (e.g. {@link ExtractAction} ) invoked through Image Gallery depend on
* what is available in the Utilities.actionsGlobalContext() lookup, we maintain
* that in sync with the local ObservableList based selection, via the
* ImageGalleryTopComponent's ExplorerManager.
*
* NOTE: When we had synchronization on selected and lastSelectedProp we got
* deadlocks with the tiles during selection
*/
public class FileIDSelectionModel {
private static final Logger LOGGER = Logger.getLogger(FileIDSelectionModel.class.getName());
private final ObservableSet<Long> selected = FXCollections.observableSet();
private final ReadOnlyObjectWrapper<Long> lastSelectedProp = new ReadOnlyObjectWrapper<>();
public FileIDSelectionModel(ImageGalleryController controller) {
/**
* Since some actions (e.g. {@link ExtractAction} ) invoked through
* Image Gallery depend on what is available in the
* Utilities.actionsGlobalContext() lookup, we maintain that in sync
* with the local ObservableList based selection, via the
* ImageGalleryTopComponent's ExplorerManager.
*/
selected.addListener((Observable observable) -> {
Set<Long> fileIDs = ImmutableSet.copyOf(selected);
SwingUtilities.invokeLater(() -> {
ArrayList<FileNode> fileNodes = new ArrayList<>();
for (Long id : fileIDs) {
try {
fileNodes.add(new FileNode(controller.getSleuthKitCase().getAbstractFileById(id)));
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to get abstract file by its ID", ex); //NON-NLS
}
}
FileNode[] fileNodeArray = fileNodes.stream().toArray(FileNode[]::new);
Children.Array children = new Children.Array();
children.add(fileNodeArray);
ImageGalleryTopComponent etc = (ImageGalleryTopComponent) WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
etc.getExplorerManager().setRootContext(new AbstractNode(children));
try {
etc.getExplorerManager().setSelectedNodes(fileNodeArray);
} catch (PropertyVetoException ex) {
LOGGER.log(Level.SEVERE, "Explorer manager selection was vetoed.", ex); //NON-NLS
}
});
});
}
public void toggleSelection(Long id) {
boolean contained = selected.contains(id);
if (contained) {
selected.remove(id);
setLastSelected(null);
} else {
selected.add(id);
setLastSelected(id);
}
}
public void clearAndSelectAll(Long... ids) {
selected.clear();
selected.addAll(Arrays.asList(ids));
if (ids.length > 0) {
setLastSelected(ids[ids.length - 1]);
} else {
setLastSelected(null);
}
}
public void clearAndSelect(Long id) {
selected.clear();
selected.add(id);
setLastSelected(id);
}
public void select(Long id) {
selected.add(id);
setLastSelected(id);
}
public void deSelect(Long id) {
selected.remove(id);
setLastSelected(null);
}
public void clearSelection() {
selected.clear();
setLastSelected(null);
}
public boolean isSelected(Long id) {
return selected.contains(id);
}
public ReadOnlyObjectProperty<Long> lastSelectedProperty() {
return lastSelectedProp.getReadOnlyProperty();
}
private void setLastSelected(Long id) {
lastSelectedProp.set(id);
}
/**
* expose the list of selected ids so that clients can listen for changes
*/
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public ObservableSet<Long> getSelected() {
return selected;
}
public void clearAndSelectAll(ObservableList<Long> ids) {
clearAndSelectAll(ids.toArray(new Long[ids.size()]));
}
}