Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop

This commit is contained in:
Raman 2018-10-09 10:49:53 -04:00
commit 8b59f526a8
9 changed files with 245 additions and 176 deletions

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-16 Basis Technology Corp.
* Copyright 2013-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -37,6 +37,7 @@ public class ImageGalleryPreferences {
*/
private static final String ENABLED_BY_DEFAULT = "enabled_by_default"; //NON-NLS
private static final String GROUP_CATEGORIZATION_WARNING_DISABLED = "group_categorization_warning_disabled"; //NON-NLS
private static final String MULTI_USER_CASE_INFO_DIALOG_DISABLED = "multi_user_case_info_dialog_disabled"; //NON-NLS
/**
* Return setting of whether Image Analyzer should be automatically enabled
@ -68,6 +69,21 @@ public class ImageGalleryPreferences {
preferences.putBoolean(GROUP_CATEGORIZATION_WARNING_DISABLED, b);
}
/**
* Return whether the dialog describing multi user case updating is
* disabled.
*
* @return true if the dialog is disabled.
*/
public static boolean isMultiUserCaseInfoDialogDisabled() {
final boolean aBoolean = preferences.getBoolean(MULTI_USER_CASE_INFO_DIALOG_DISABLED, false);
return aBoolean;
}
public static void setMultiUserCaseInfoDialogDisabled(boolean b) {
preferences.putBoolean(MULTI_USER_CASE_INFO_DIALOG_DISABLED, b);
}
static void addChangeListener(PreferenceChangeListener l) {
preferences.addPreferenceChangeListener(l);
}

View File

@ -18,14 +18,25 @@
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.util.concurrent.ListenableFuture;
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javax.annotation.concurrent.ThreadSafe;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
@ -34,15 +45,19 @@ import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TskCoreException;
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction")
@ -119,9 +134,13 @@ public final class OpenAction extends CallableSystemAction {
}
@Override
@NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"})
@NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery",
"OpenAction.multiUserDialog.Header=Multi-user Image Gallery",
"OpenAction.multiUserDialog.ContentText=The Image Gallery updates itself differently for multi-user cases than single user cases. Notably:\n\n"
+ "If your computer is analyzing a data source, then you will get real-time Image Gallery updates as files are analyzed (hashed, EXIF, etc.). This is the same behavior as a single-user case.\n\n"
+ "If another computer in your multi-user cluster is analyzing a data source, you will get updates about files on that data source only when you launch Image Gallery, which will cause the local database to be rebuilt based on results from other nodes.",
"OpenAction.multiUserDialog.checkBox.text=Don't show this message again."})
public void performAction() {
//check case
final Case currentCase;
try {
@ -130,57 +149,103 @@ public final class OpenAction extends CallableSystemAction {
logger.log(Level.SEVERE, "Exception while getting open case.", ex);
return;
}
ImageGalleryController controller;
try {
ImageGalleryController controller = ImageGalleryModule.getController();
if (controller.isDataSourcesTableStale()) {
//drawable db is stale, ask what to do
int answer = JOptionPane.showConfirmDialog(
WindowManager.getDefault().getMainWindow(),
Bundle.OpenAction_stale_confDlg_msg(),
Bundle.OpenAction_stale_confDlg_title(),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
controller = ImageGalleryModule.getController();
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting ImageGalleryController for current case.", ex);
return;
}
Platform.runLater(() -> {
if (currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE
&& ImageGalleryPreferences.isMultiUserCaseInfoDialogDisabled() == false) {
Alert dialog = new Alert(Alert.AlertType.INFORMATION);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setResizable(true);
dialog.setTitle(Bundle.OpenAction_dialogTitle());
dialog.setHeaderText(Bundle.OpenAction_multiUserDialog_Header());
switch (answer) {
case JOptionPane.YES_OPTION:
/* For a single-user case, we favor user experience, and
* rebuild the database as soon as Image Gallery is
* enabled for the case. For a multi-user case, we favor
* overall performance and user experience, not every
* user may want to review images, so we rebuild the
* database only when a user launches Image Gallery.
*/
if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
/*
* Turning listening off is necessary in order to
* invoke the listener that will call
Label label = new Label(Bundle.OpenAction_multiUserDialog_ContentText());
label.setMaxWidth(450);
label.setWrapText(true);
CheckBox dontShowAgainCheckBox = new CheckBox(Bundle.OpenAction_multiUserDialog_checkBox_text());
dialog.getDialogPane().setContent(new VBox(10, label, dontShowAgainCheckBox));
GuiUtils.setDialogIcons(dialog);
dialog.showAndWait();
if (dialog.getResult() == ButtonType.OK && dontShowAgainCheckBox.isSelected()) {
ImageGalleryPreferences.setMultiUserCaseInfoDialogDisabled(true);
}
}
checkDBStale(controller);
});
}
private void checkDBStale(ImageGalleryController controller) {
//check if db is stale on throw away bg thread and then react back on jfx thread.
ListenableFuture<Boolean> staleFuture = TaskUtils.getExecutorForClass(OpenAction.class)
.submit(controller::isDataSourcesTableStale);
addFXCallback(staleFuture,
dbIsStale -> {
//back on fx thread.
if (false == dbIsStale) {
//drawable db is not stale, just open it
openTopComponent();
} else {
//drawable db is stale, ask what to do
Alert alert = new Alert(Alert.AlertType.WARNING,
Bundle.OpenAction_stale_confDlg_msg(),
ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
alert.initModality(Modality.APPLICATION_MODAL);
alert.setTitle(Bundle.OpenAction_stale_confDlg_title());
GuiUtils.setDialogIcons(alert);
ButtonType answer = alert.showAndWait().orElse(ButtonType.CANCEL);
if (answer == ButtonType.CANCEL) {
//just do nothing
} else if (answer == ButtonType.NO) {
openTopComponent();
} else if (answer == ButtonType.YES) {
if (controller.getAutopsyCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
/* For a single-user case, we favor user
* experience, and rebuild the database as soon
* as Image Gallery is enabled for the case.
*
* Turning listening off is necessary in order
* to invoke the listener that will call
* controller.rebuildDB();
*/
controller.setListeningEnabled(false);
controller.setListeningEnabled(true);
} else {
/*
* For a multi-user case, we favor overall
* performance and user experience, not every
* user may want to review images, so we rebuild
* the database only when a user launches Image
* Gallery.
*/
controller.rebuildDB();
}
ImageGalleryTopComponent.openTopComponent();
break;
openTopComponent();
}
}
},
throwable -> logger.log(Level.SEVERE, "Error checking if drawable db is stale.", throwable)//NON-NLS
);
}
case JOptionPane.NO_OPTION: {
private void openTopComponent() {
SwingUtilities.invokeLater(() -> {
try {
ImageGalleryTopComponent.openTopComponent();
}
break;
case JOptionPane.CANCEL_OPTION:
break; //do nothing
}
} else {
//drawable db is not stale, just open it
ImageGalleryTopComponent.openTopComponent();
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex);//NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS}
}
});
}
@Override
@ -195,6 +260,6 @@ public final class OpenAction extends CallableSystemAction {
@Override
public boolean asynchronous() {
return false; // run on edt
return true; // run off edt
}
}

View File

@ -99,7 +99,7 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
if (!Objects.equals(this.attr, other.attr)) {
return false;
}
return Objects.equals(this.dataSource, other.dataSource);
return this.dataSource.getId() == other.dataSource.getId();
}
@Override

View File

@ -82,7 +82,7 @@ public class SummaryTablePane extends AnchorPane {
//register for category events
controller.getCategoryManager().registerListener(this);
handleCategoryChanged(null);
new Thread(() -> handleCategoryChanged(null)).start();
}
public SummaryTablePane(ImageGalleryController controller) {

View File

@ -74,6 +74,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.DataSource;
/**
@ -255,6 +256,7 @@ public class Toolbar extends ToolBar {
evt -> {
Platform.runLater(() -> {
Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem();
//restore selection once the sync is done.
syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater);
});
});
@ -269,22 +271,19 @@ public class Toolbar extends ToolBar {
}
private void initTagMenuButton() {
ListenableFuture<TagGroupAction> future = exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller));
Futures.addCallback(future, new FutureCallback<TagGroupAction>() {
@Override
public void onSuccess(TagGroupAction followUpGroupAction) {
addFXCallback(exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)),
followUpGroupAction -> {
//on fx thread
tagGroupMenuButton.setOnAction(followUpGroupAction);
tagGroupMenuButton.setText(followUpGroupAction.getText());
tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic());
}
@Override
public void onFailure(Throwable throwable) {
},
throwable -> {
/*
* The problem appears to be a timing issue where a case is
* closed before this initialization is completed, which It
* appears to be harmless, so we are temporarily changing this
* log message to a WARNING.
* appears to be harmless, so we are temporarily changing
* this log message to a WARNING.
*
* TODO (JIRA-3010): SEVERE error logged by image Gallery UI
*/
@ -295,7 +294,7 @@ public class Toolbar extends ToolBar {
logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS
}
}
}, Platform::runLater);
);
tagGroupMenuButton.showingProperty().addListener(showing -> {
if (tagGroupMenuButton.isShowing()) {
@ -303,24 +302,18 @@ public class Toolbar extends ToolBar {
return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller)));
});
Futures.addCallback(getTagsFuture, new FutureCallback<List<MenuItem>>() {
@Override
public void onSuccess(List<MenuItem> result) {
tagGroupMenuButton.getItems().setAll(result);
}
@Override
public void onFailure(Throwable t) {
logger.log(Level.SEVERE, "Error getting non-gategory tag names.", t);
}
}, Platform::runLater);
addFXCallback(getTagsFuture,
menuItems -> tagGroupMenuButton.getItems().setAll(menuItems),
throwable -> logger.log(Level.SEVERE, "Error getting non-gategory tag names.", throwable)
);
}
});
}
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
private ListenableFuture<List<DataSource>> syncDataSources() {
ListenableFuture<List<DataSource>> future = exec.submit(() -> {
ListenableFuture<List<DataSource>> dataSourcesFuture = exec.submit(() -> {
List<DataSource> dataSourcesInCase = controller.getSleuthKitCase().getDataSources();
synchronized (dataSourcesViewable) {
dataSourcesViewable.clear();
@ -331,22 +324,18 @@ public class Toolbar extends ToolBar {
}
return dataSourcesInCase;
});
Futures.addCallback(future, new FutureCallback<List<DataSource>>() {
@Override
public void onSuccess(List<DataSource> result) {
List<Optional<DataSource>> newDataSources = new ArrayList<>();
newDataSources.add(Optional.empty());
result.forEach(dataSource -> newDataSources.add(Optional.of(dataSource)));
addFXCallback(dataSourcesFuture,
result -> {
//on fx thread
List<Optional<DataSource>> newDataSources = new ArrayList<>(Lists.transform(result, Optional::of));
newDataSources.add(0, Optional.empty());
dataSources.setAll(newDataSources);
}
},
throwable -> logger.log(Level.SEVERE, "Unable to get datasources for current case.", throwable) //NON-NLS
@Override
public void onFailure(Throwable t) {
logger.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS
}
}, Platform::runLater);
);
return future;
return dataSourcesFuture;
}
/**
@ -384,8 +373,7 @@ public class Toolbar extends ToolBar {
* selection.
*/
private void syncGroupControlsEnabledState(GroupViewState newViewState) {
boolean noGroupSelected = (null == newViewState)
|| (null == newViewState.getGroup());
boolean noGroupSelected = (null == newViewState) || (null == newViewState.getGroup());
Platform.runLater(() -> {
tagGroupMenuButton.setDisable(noGroupSelected);
catGroupMenuButton.setDisable(noGroupSelected);
@ -402,7 +390,5 @@ public class Toolbar extends ToolBar {
public Toolbar(ImageGalleryController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
}
}

View File

@ -21,12 +21,12 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import static com.google.common.collect.Lists.transform;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -35,6 +35,7 @@ import java.util.Map;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.IntStream;
import javafx.animation.Interpolator;
@ -135,7 +136,10 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -254,7 +258,7 @@ public class GroupPane extends BorderPane {
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
/**
* map from fileIDs to their assigned cells in the tile view. This is used
* Map from fileIDs to their assigned cells in the tile view. This is used
* to determine whether fileIDs are visible or are offscreen. No entry
* indicates the given fileID is not displayed on screen. DrawableCells are
* responsible for adding and removing themselves from this map.
@ -371,7 +375,7 @@ public class GroupPane extends BorderPane {
case FIVE:
return cat5Toggle;
default:
throw new IllegalArgumentException(category.name());
throw new UnsupportedOperationException("Unknown category: " + category.name());
}
}
@ -425,51 +429,41 @@ public class GroupPane extends BorderPane {
DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75);
gridView.cellHeightProperty().bind(cellSize);
gridView.cellWidthProperty().bind(cellSize);
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
gridView.setCellFactory(param -> new DrawableCell());
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS
Platform.runLater(() -> {
addFXCallback(exec.submit(() -> controller.getTagsManager().getFollowUpTagName()),
followUpTagName -> {
//on fx thread
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(followUpTagName, controller);
tagSelectedSplitMenu.setText(followUpSelectedAction.getText());
tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic());
tagSelectedSplitMenu.setOnAction(followUpSelectedAction);
tagSelectedSplitMenu.showingProperty().addListener(showing -> {
if (tagSelectedSplitMenu.isShowing()) {
ListenableFuture<List<MenuItem>> getTagsFuture = exec.submit(()
-> Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
tagName -> GuiUtils.createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller))));
Futures.addCallback(getTagsFuture, new FutureCallback<List<MenuItem>>() {
@Override
public void onSuccess(List<MenuItem> result) {
tagSelectedSplitMenu.getItems().setAll(result);
}
@Override
public void onFailure(Throwable throwable) {
logger.log(Level.SEVERE, "Error getting tag names.", throwable);
}
}, Platform::runLater);
}
});
});
},
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable));
addFXCallback(exec.submit(() -> controller.getTagsManager().getNonCategoryTagNames()),
tagNames -> {
//on fx thread
List<MenuItem> menuItems = transform(tagNames,
tagName -> createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller)));
tagSelectedSplitMenu.getItems().setAll(menuItems);
},
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable)//NON-NLS
);
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
catSelectedSplitMenu.showingProperty().addListener(showing -> {
if (catSelectedSplitMenu.isShowing()) {
List<MenuItem> categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()),
cat -> GuiUtils.createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
List<MenuItem> categoryMenues = transform(asList(DhsImageCategory.values()),
cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
catSelectedSplitMenu.getItems().setAll(categoryMenues);
}
});
slideShowToggle.getStyleClass().remove("radio-button");
slideShowToggle.getStyleClass().add("toggle-button");
@ -681,7 +675,6 @@ public class GroupPane extends BorderPane {
}
void makeSelection(Boolean shiftDown, Long newFileID) {
if (shiftDown) {
//TODO: do more hear to implement slicker multiselect
int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
@ -694,7 +687,6 @@ public class GroupPane extends BorderPane {
} else {
selectionAnchorIndex = null;
selectionModel.clearAndSelect(newFileID);
}
}

View File

@ -22,6 +22,7 @@ import com.google.common.eventbus.Subscribe;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static java.util.Collections.singletonMap;
import java.util.List;
import java.util.Objects;
import static java.util.Objects.isNull;
@ -112,9 +113,7 @@ public class MetaDataPane extends DrawableUIBase {
});
copyMenuItem.setAccelerator(COPY_KEY_COMBINATION);
copyMenuItem.setOnAction(actionEvent -> {
copyValueToClipBoard();
});
copyMenuItem.setOnAction(actionEvent -> copyValueToClipBoard());
tableView.setContextMenu(contextMenu);
tableView.setOnKeyPressed((KeyEvent event) -> {
@ -220,9 +219,6 @@ public class MetaDataPane extends DrawableUIBase {
return imageBorder;
}
/**
* {@inheritDoc }
*/
@Subscribe
@Override
public void handleCategoryChanged(CategoryManager.CategoryChangeEvent evt) {
@ -256,9 +252,9 @@ public class MetaDataPane extends DrawableUIBase {
private void copyValueToClipBoard() {
Pair<DrawableAttribute<?>, Collection<?>> selectedItem = tableView.getSelectionModel().getSelectedItem();
if (nonNull(selectedItem)) {
Clipboard.getSystemClipboard().setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT,
getValueDisplayString(selectedItem)));
Clipboard.getSystemClipboard().setContent(
singletonMap(DataFormat.PLAIN_TEXT, getValueDisplayString(selectedItem))
);
}
}
}

View File

@ -131,8 +131,8 @@ public class SlideShowView extends DrawableTileBase {
getGroupPane().grouping().addListener(observable -> {
syncButtonVisibility();
if (getGroupPane().getGroup() != null) {
getGroupPane().getGroup().getFileIDs().addListener((Observable observable1) ->
syncButtonVisibility());
getGroupPane().getGroup().getFileIDs().addListener((Observable observable1)
-> syncButtonVisibility());
}
});
}
@ -215,9 +215,7 @@ public class SlideShowView extends DrawableTileBase {
mediaTask = null;
}
});
myTask.setOnCancelled(cancelled -> {
disposeContent();
});
myTask.setOnCancelled(cancelled -> disposeContent());
exec.execute(myTask);
return progressNode;
@ -245,7 +243,6 @@ public class SlideShowView extends DrawableTileBase {
/**
*
* @param file the value of file
* @param imageTask the value of imageTask
*/
@Override
@ -259,9 +256,6 @@ public class SlideShowView extends DrawableTileBase {
return maskerPane;
}
/**
* {@inheritDoc }
*/
@Override
protected String getTextForLabel() {
return getFile().map(DrawableFile::getName).orElse("") + " " + getSupplementalText();
@ -301,9 +295,6 @@ public class SlideShowView extends DrawableTileBase {
}
/**
* {@inheritDoc }
*/
@Override
@ThreadConfined(type = ThreadType.ANY)
public DhsImageCategory updateCategory() {

View File

@ -18,11 +18,16 @@
*/
package org.sleuthkit.autopsy.imagegallery.utils;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.concurrent.Task;
/**
@ -46,4 +51,22 @@ public final class TaskUtils {
return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("Image Gallery " + clazz.getSimpleName() + " BG Thread").build()));
}
public static <X> void addFXCallback(ListenableFuture<X> future, Consumer<X> onSuccess, Consumer<Throwable> onFailure) {
Futures.addCallback(future, makeFutureCallBack(onSuccess, onFailure), Platform::runLater);
}
public static <X> FutureCallback< X> makeFutureCallBack(Consumer<X> onSuccess, Consumer<Throwable> onFailure) {
return new FutureCallback<X>() {
@Override
public void onSuccess(X result) {
onSuccess.accept(result);
}
@Override
public void onFailure(Throwable t) {
onFailure.accept(t);
}
};
}
}