mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop
This commit is contained in:
commit
8b59f526a8
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user