mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 01:07:42 +00:00
Merge remote-tracking branch 'refs/remotes/sleuthkit/develop' into develop
This commit is contained in:
commit
a2312c92e0
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2011 Basis Technology Corp.
|
* Copyright 2011-16 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -95,16 +95,18 @@ class ThumbnailViewNode extends FilterNode {
|
|||||||
super.done();
|
super.done();
|
||||||
try {
|
try {
|
||||||
iconCache = new SoftReference<>(super.get());
|
iconCache = new SoftReference<>(super.get());
|
||||||
progressHandle.finish();
|
|
||||||
fireIconChange();
|
fireIconChange();
|
||||||
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
|
Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon", ex); //NON-NLS
|
||||||
|
} finally {
|
||||||
|
progressHandle.finish();
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
timer.stop();
|
timer.stop();
|
||||||
timer = null;
|
timer = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
swingWorker = null;
|
||||||
Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon", ex); //NON-NLS
|
|
||||||
}
|
}
|
||||||
swingWorker = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
swingWorker.execute();
|
swingWorker.execute();
|
||||||
|
@ -20,8 +20,4 @@ PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, fre
|
|||||||
PlatformUtil.getAllMemUsageInfo.usageText={0}\n\
|
PlatformUtil.getAllMemUsageInfo.usageText={0}\n\
|
||||||
{1}\n\
|
{1}\n\
|
||||||
Process Virtual Memory\: {2}
|
Process Virtual Memory\: {2}
|
||||||
StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract
|
StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract
|
||||||
ImageUtils.GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}
|
|
||||||
ImageUtils.GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}
|
|
||||||
ImageUtils.ReadImageTask.mesage.text=Reading image\: {0}
|
|
||||||
VideoUtils.genVideoThumb.progress.text=extracting temporary file {0}
|
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2012-15 Basis Technology Corp.
|
* Copyright 2012-16 Basis Technology Corp.
|
||||||
*
|
*
|
||||||
* Copyright 2012 42six Solutions.
|
* Copyright 2012 42six Solutions.
|
||||||
* Contact: aebadirad <at> 42six <dot> com
|
* Contact: aebadirad <at> 42six <dot> com
|
||||||
@ -30,6 +30,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -74,10 +75,6 @@ public class ImageUtils {
|
|||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName());
|
||||||
|
|
||||||
private static final String COULD_NOT_WRITE_CACHE_THUMBNAIL = "Could not write cache thumbnail: "; //NOI18N NON-NLS
|
|
||||||
private static final String COULD_NOT_CREATE_IMAGE_INPUT_STREAM = "Could not create ImageInputStream."; //NOI18N NON-NLS
|
|
||||||
private static final String NO_IMAGE_READER_FOUND_FOR_ = "No ImageReader found for "; //NOI18N NON-NLS
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save thumbnails to disk as this format
|
* save thumbnails to disk as this format
|
||||||
*/
|
*/
|
||||||
@ -594,7 +591,7 @@ public class ImageUtils {
|
|||||||
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
|
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
|
||||||
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
|
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
IIOException iioException = new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM);
|
IIOException iioException = new IIOException("Could not create ImageInputStream.");
|
||||||
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
|
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
|
||||||
throw iioException;
|
throw iioException;
|
||||||
}
|
}
|
||||||
@ -613,7 +610,7 @@ public class ImageUtils {
|
|||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
IIOException iioException = new IIOException(NO_IMAGE_READER_FOUND_FOR_ + getContentPathSafe(file));
|
IIOException iioException = new IIOException("No ImageReader found.");
|
||||||
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
|
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
|
||||||
|
|
||||||
throw iioException;
|
throw iioException;
|
||||||
@ -646,18 +643,19 @@ public class ImageUtils {
|
|||||||
*/
|
*/
|
||||||
static private class GetThumbnailTask extends ReadImageTaskBase {
|
static private class GetThumbnailTask extends ReadImageTaskBase {
|
||||||
|
|
||||||
private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read image for thumbnail generation."; //NOI18N NON-NLS
|
private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read {0} for thumbnail generation."; //NOI18N NON-NLS
|
||||||
|
|
||||||
private final int iconSize;
|
private final int iconSize;
|
||||||
private final File cacheFile;
|
private final File cacheFile;
|
||||||
private final boolean defaultOnFailure;
|
private final boolean defaultOnFailure;
|
||||||
|
|
||||||
// @NbBundle.Messages({"# {0} - file name",
|
@NbBundle.Messages({"# {0} - file name",
|
||||||
// "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", "# {0} - file name",
|
"GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
|
||||||
// "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
|
"# {0} - file name",
|
||||||
|
"GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
|
||||||
private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
|
private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
|
||||||
super(file);
|
super(file);
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.loadingThumbnailFor", file.getName()));
|
updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
|
||||||
this.iconSize = iconSize;
|
this.iconSize = iconSize;
|
||||||
this.defaultOnFailure = defaultOnFailure;
|
this.defaultOnFailure = defaultOnFailure;
|
||||||
this.cacheFile = getCachedThumbnailLocation(file.getId());
|
this.cacheFile = getCachedThumbnailLocation(file.getId());
|
||||||
@ -678,36 +676,39 @@ public class ImageUtils {
|
|||||||
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
|
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
|
||||||
return SwingFXUtils.toFXImage(cachedThumbnail, null);
|
return SwingFXUtils.toFXImage(cachedThumbnail, null);
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (Exception ex) {
|
||||||
LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
LOGGER.log(Level.WARNING, "ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
||||||
|
cacheFile.delete(); //since we can't read the file we might as well delete it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//There was no correctly-sized cached thumbnail so make one.
|
//There was no correctly-sized cached thumbnail so make one.
|
||||||
BufferedImage thumbnail = null;
|
BufferedImage thumbnail = null;
|
||||||
|
|
||||||
if (VideoUtils.isVideoThumbnailSupported(file)) {
|
if (VideoUtils.isVideoThumbnailSupported(file)) {
|
||||||
if (openCVLoaded) {
|
if (openCVLoaded) {
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.generatingPreviewFor", file.getName()));
|
updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
|
||||||
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
|
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
|
||||||
}
|
}
|
||||||
if (null == thumbnail) {
|
if (null == thumbnail) {
|
||||||
if (defaultOnFailure) {
|
if (defaultOnFailure) {
|
||||||
thumbnail = DEFAULT_THUMBNAIL;
|
thumbnail = DEFAULT_THUMBNAIL;
|
||||||
} else {
|
} else {
|
||||||
throw new IIOException("Failed to generate thumbnail for video file.");
|
throw new IIOException("Failed to generate a thumbnail for " + getContentPathSafe(file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//read the image into a buffered image.
|
//read the image into a buffered image.
|
||||||
|
//TODO: I don't like this, we just converted it from BufferedIamge to fx Image -jm
|
||||||
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null);
|
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null);
|
||||||
if (null == bufferedImage) {
|
if (null == bufferedImage) {
|
||||||
LOGGER.log(Level.WARNING, FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION);
|
String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
|
||||||
throw new IIOException(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION);
|
LOGGER.log(Level.WARNING, msg);
|
||||||
|
throw new IIOException(msg);
|
||||||
}
|
}
|
||||||
updateProgress(-1, 1);
|
updateProgress(-1, 1);
|
||||||
|
|
||||||
@ -716,23 +717,21 @@ public class ImageUtils {
|
|||||||
thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
|
thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
|
||||||
} catch (IllegalArgumentException | OutOfMemoryError e) {
|
} catch (IllegalArgumentException | OutOfMemoryError e) {
|
||||||
// if resizing does not work due to extreme aspect ratio or oom, crop the image instead.
|
// if resizing does not work due to extreme aspect ratio or oom, crop the image instead.
|
||||||
LOGGER.log(Level.WARNING, "Could not scale image {0}: " + e.toString() + ". Attemptying to crop {0} instead", ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
LOGGER.log(Level.WARNING, "Cropping {0}, because it could not be scaled: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
||||||
|
|
||||||
final int height = bufferedImage.getHeight();
|
final int height = bufferedImage.getHeight();
|
||||||
final int width = bufferedImage.getWidth();
|
final int width = bufferedImage.getWidth();
|
||||||
if (iconSize < height || iconSize < width) {
|
if (iconSize < height || iconSize < width) {
|
||||||
final int cropHeight = Math.min(iconSize, height);
|
final int cropHeight = Math.min(iconSize, height);
|
||||||
final int cropWidth = Math.min(iconSize, width);
|
final int cropWidth = Math.min(iconSize, width);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
|
thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
|
||||||
} catch (Exception cropException) {
|
} catch (Exception cropException) {
|
||||||
LOGGER.log(Level.WARNING, "Could not crop image {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
||||||
throw cropException;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "Could not scale image {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
LOGGER.log(Level.WARNING, "Could not scale {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -744,7 +743,7 @@ public class ImageUtils {
|
|||||||
updateProgress(-1, 1);
|
updateProgress(-1, 1);
|
||||||
|
|
||||||
//if we got a valid thumbnail save it
|
//if we got a valid thumbnail save it
|
||||||
if ((cacheFile != null) && nonNull(thumbnail) && DEFAULT_THUMBNAIL != thumbnail) {
|
if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
|
||||||
saveThumbnail(thumbnail);
|
saveThumbnail(thumbnail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -790,16 +789,16 @@ public class ImageUtils {
|
|||||||
/**
|
/**
|
||||||
* A task that reads the content of a AbstractFile as a javafx Image.
|
* A task that reads the content of a AbstractFile as a javafx Image.
|
||||||
*/
|
*/
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"# {0} - file name",
|
||||||
|
"ReadImageTask.mesageText=Reading image: {0}"})
|
||||||
static private class ReadImageTask extends ReadImageTaskBase {
|
static private class ReadImageTask extends ReadImageTaskBase {
|
||||||
|
|
||||||
ReadImageTask(AbstractFile file) {
|
ReadImageTask(AbstractFile file) {
|
||||||
super(file);
|
super(file);
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.ReadImageTask.mesage.text", file.getName()));
|
updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// @NbBundle.Messages({
|
|
||||||
// "# {0} - file name",
|
|
||||||
// "LoadImageTask.mesageText=Reading image: {0}"})
|
|
||||||
@Override
|
@Override
|
||||||
protected javafx.scene.image.Image call() throws Exception {
|
protected javafx.scene.image.Image call() throws Exception {
|
||||||
return readImage();
|
return readImage();
|
||||||
@ -811,70 +810,55 @@ public class ImageUtils {
|
|||||||
*/
|
*/
|
||||||
static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
|
static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
|
||||||
|
|
||||||
private static final String IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT = "ImageUtils could not read {0}. It may be unsupported or corrupt"; //NOI18N NON-NLS
|
private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NOI18N NON-NLS
|
||||||
final AbstractFile file;
|
final AbstractFile file;
|
||||||
private ImageReader reader;
|
// private ImageReader reader;
|
||||||
|
|
||||||
ReadImageTaskBase(AbstractFile file) {
|
ReadImageTaskBase(AbstractFile file) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected javafx.scene.image.Image readImage() throws IOException {
|
protected javafx.scene.image.Image readImage() throws IOException {
|
||||||
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
|
if (ImageUtils.isGIF(file)) {
|
||||||
if (ImageUtils.isGIF(file)) {
|
//use JavaFX to directly read GIF to preserve potential animation
|
||||||
//use JavaFX to directly read GIF to preserve potential animation,
|
javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(new ReadContentInputStream(file)));
|
||||||
javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(inputStream));
|
if (image.isError() == false) {
|
||||||
if (image.isError() == false) {
|
return image;
|
||||||
return image;
|
|
||||||
}
|
|
||||||
//fall through to default image reading code if there was an error
|
|
||||||
}
|
}
|
||||||
if (isCancelled()) {
|
//fall through to default image reading code if there was an error
|
||||||
return null;
|
}
|
||||||
}
|
if (isCancelled()) {
|
||||||
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
|
return null;
|
||||||
if (input == null) {
|
}
|
||||||
throw new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM);
|
|
||||||
}
|
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
|
||||||
|
|
||||||
//we use the first ImageReader, is there any point to trying the others?
|
return getImageProperty(file, "ImageIO could not read {0}: ",
|
||||||
if (readers.hasNext()) {
|
imageReader -> {
|
||||||
reader = readers.next();
|
imageReader.addIIOReadProgressListener(ReadImageTaskBase.this);
|
||||||
reader.addIIOReadProgressListener(this);
|
|
||||||
reader.setInput(input);
|
|
||||||
/*
|
/*
|
||||||
* This is the important part, get or create a
|
* This is the important part, get or create a
|
||||||
* ImageReadParam, create a destination image to hold
|
* ImageReadParam, create a destination image to hold
|
||||||
* the decoded result, then pass that image with the
|
* the decoded result, then pass that image with the
|
||||||
* param.
|
* param.
|
||||||
*/
|
*/
|
||||||
ImageReadParam param = reader.getDefaultReadParam();
|
ImageReadParam param = imageReader.getDefaultReadParam();
|
||||||
|
BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
|
||||||
BufferedImage bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
|
|
||||||
param.setDestination(bufferedImage);
|
param.setDestination(bufferedImage);
|
||||||
try {
|
try {
|
||||||
bufferedImage = reader.read(0, param); //should always be same bufferedImage object
|
bufferedImage = imageReader.read(0, param); //should always be same bufferedImage object
|
||||||
} catch (IOException iOException) {
|
} catch (IOException iOException) {
|
||||||
// Ignore this exception or display a warning or similar, for exceptions happening during decoding
|
LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
|
||||||
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
|
|
||||||
} finally {
|
} finally {
|
||||||
reader.removeIIOReadProgressListener(this);
|
imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this);
|
||||||
reader.dispose();
|
|
||||||
}
|
}
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return SwingFXUtils.toFXImage(bufferedImage, null);
|
return SwingFXUtils.toFXImage(bufferedImage, null);
|
||||||
} else {
|
});
|
||||||
throw new IIOException(NO_IMAGE_READER_FOUND_FOR_ + ImageUtils.getContentPathSafe(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void imageProgress(ImageReader source, float percentageDone) {
|
public void imageProgress(ImageReader reader, float percentageDone) {
|
||||||
//update this task with the progress reported by ImageReader.read
|
//update this task with the progress reported by ImageReader.read
|
||||||
updateProgress(percentageDone, 100);
|
updateProgress(percentageDone, 100);
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
@ -890,11 +874,11 @@ public class ImageUtils {
|
|||||||
try {
|
try {
|
||||||
javafx.scene.image.Image fxImage = get();
|
javafx.scene.image.Image fxImage = get();
|
||||||
if (fxImage == null) {
|
if (fxImage == null) {
|
||||||
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT, ImageUtils.getContentPathSafe(file));
|
LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT, ImageUtils.getContentPathSafe(file));
|
||||||
} else {
|
} else {
|
||||||
if (fxImage.isError()) {
|
if (fxImage.isError()) {
|
||||||
//if there was somekind of error, log it
|
//if there was somekind of error, log it
|
||||||
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file));
|
LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
@ -905,7 +889,7 @@ public class ImageUtils {
|
|||||||
@Override
|
@Override
|
||||||
protected void failed() {
|
protected void failed() {
|
||||||
super.failed();
|
super.failed();
|
||||||
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file));
|
LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -950,7 +934,7 @@ public class ImageUtils {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private static String getContentPathSafe(Content content) {
|
static String getContentPathSafe(Content content) {
|
||||||
try {
|
try {
|
||||||
return content.getUniquePath();
|
return content.getUniquePath();
|
||||||
} catch (TskCoreException tskCoreException) {
|
} catch (TskCoreException tskCoreException) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2015 Basis Technology Corp.
|
* Copyright 2015-16 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.coreutils;
|
package org.sleuthkit.autopsy.coreutils;
|
||||||
|
|
||||||
|
import com.google.common.io.Files;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -91,33 +92,33 @@ public class VideoUtils {
|
|||||||
return isMediaThumbnailSupported(file, SUPPORTED_VIDEO_MIME_TYPES, SUPPORTED_VIDEO_EXTENSIONS, CONDITIONAL_MIME_TYPES);
|
return isMediaThumbnailSupported(file, SUPPORTED_VIDEO_MIME_TYPES, SUPPORTED_VIDEO_EXTENSIONS, CONDITIONAL_MIME_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({"# {0} - file name",
|
||||||
|
"VideoUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
|
||||||
static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) {
|
static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) {
|
||||||
java.io.File tempFile = getTempVideoFile(file);
|
java.io.File tempFile = getTempVideoFile(file);
|
||||||
|
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
|
||||||
try {
|
ProgressHandle progress = ProgressHandleFactory.createHandle(Bundle.VideoUtils_genVideoThumb_progress_text(file.getName()));
|
||||||
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
|
progress.start(100);
|
||||||
com.google.common.io.Files.createParentDirs(tempFile);
|
try {
|
||||||
ProgressHandle progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(VideoUtils.class, "VideoUtils.genVideoThumb.progress.text", file.getName()));
|
Files.createParentDirs(tempFile);
|
||||||
progress.start(100);
|
ContentUtils.writeToFile(file, tempFile, progress, null, true);
|
||||||
try {
|
} catch (IOException ex) {
|
||||||
ContentUtils.writeToFile(file, tempFile, progress, null, true);
|
LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS
|
||||||
} catch (IOException ex) {
|
} finally {
|
||||||
LOGGER.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
|
|
||||||
}
|
|
||||||
progress.finish();
|
progress.finish();
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoCapture videoFile = new VideoCapture(); // will contain the video
|
VideoCapture videoFile = new VideoCapture(); // will contain the video
|
||||||
|
|
||||||
if (!videoFile.open(tempFile.toString())) {
|
if (!videoFile.open(tempFile.toString())) {
|
||||||
|
LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second
|
double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second
|
||||||
double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames
|
double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames
|
||||||
if (fps <= 0 || totalFrames <= 0) {
|
if (fps <= 0 || totalFrames <= 0) {
|
||||||
|
LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
double milliseconds = 1000 * (totalFrames / fps); //total milliseconds
|
double milliseconds = 1000 * (totalFrames / fps); //total milliseconds
|
||||||
@ -132,10 +133,12 @@ public class VideoUtils {
|
|||||||
for (int x = 0; x < THUMB_COLUMNS; x++) {
|
for (int x = 0; x < THUMB_COLUMNS; x++) {
|
||||||
for (int y = 0; y < THUMB_ROWS; y++) {
|
for (int y = 0; y < THUMB_ROWS; y++) {
|
||||||
if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) {
|
if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) {
|
||||||
|
LOGGER.log(Level.WARNING, "Error seeking to " + timestamp + "ms in {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||||
break; // if we can't set the time, return black for that frame
|
break; // if we can't set the time, return black for that frame
|
||||||
}
|
}
|
||||||
//read the frame into the image/matrix
|
//read the frame into the image/matrix
|
||||||
if (!videoFile.read(imageMatrix)) {
|
if (!videoFile.read(imageMatrix)) {
|
||||||
|
LOGGER.log(Level.WARNING, "Error reading frames at " + timestamp + "ms from {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||||
break; //if the image for some reason is bad, return black for that frame
|
break; //if the image for some reason is bad, return black for that frame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +107,8 @@ public enum FileTypeUtils {
|
|||||||
, "sn", "ras" //sun raster NON-NLS
|
, "sn", "ras" //sun raster NON-NLS
|
||||||
, "ico" //windows icons NON-NLS
|
, "ico" //windows icons NON-NLS
|
||||||
, "tga" //targa NON-NLS
|
, "tga" //targa NON-NLS
|
||||||
|
, "wmf", "emf" // windows meta file NON-NLS
|
||||||
|
, "wmz", "emz" //compressed windows meta file NON-NLS
|
||||||
));
|
));
|
||||||
|
|
||||||
//add list of known video extensions
|
//add list of known video extensions
|
||||||
@ -129,6 +131,8 @@ public enum FileTypeUtils {
|
|||||||
* mime types.
|
* mime types.
|
||||||
*/
|
*/
|
||||||
supportedMimeTypes.addAll(Arrays.asList("application/x-123")); //NON-NLS
|
supportedMimeTypes.addAll(Arrays.asList("application/x-123")); //NON-NLS
|
||||||
|
supportedMimeTypes.addAll(Arrays.asList("application/x-wmf")); //NON-NLS
|
||||||
|
supportedMimeTypes.addAll(Arrays.asList("application/x-emf")); //NON-NLS
|
||||||
|
|
||||||
//add list of mimetypes ImageIO claims to support
|
//add list of mimetypes ImageIO claims to support
|
||||||
supportedMimeTypes.addAll(Stream.of(ImageIO.getReaderMIMETypes())
|
supportedMimeTypes.addAll(Stream.of(ImageIO.getReaderMIMETypes())
|
||||||
|
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.imagegallery;
|
|||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -54,7 +55,6 @@ import javafx.scene.layout.StackPane;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.netbeans.api.progress.ProgressHandle;
|
import org.netbeans.api.progress.ProgressHandle;
|
||||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||||
import org.openide.util.Cancellable;
|
import org.openide.util.Cancellable;
|
||||||
@ -85,6 +85,7 @@ import org.sleuthkit.datamodel.Image;
|
|||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects different parts of ImageGallery together and is hub for flow of
|
* Connects different parts of ImageGallery together and is hub for flow of
|
||||||
@ -127,8 +128,6 @@ public final class ImageGalleryController implements Executor {
|
|||||||
*/
|
*/
|
||||||
private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false);
|
private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0);
|
|
||||||
|
|
||||||
private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false);
|
private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false);
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
@ -153,7 +152,6 @@ public final class ImageGalleryController implements Executor {
|
|||||||
|
|
||||||
private Node infoOverlay;
|
private Node infoOverlay;
|
||||||
private SleuthkitCase sleuthKitCase;
|
private SleuthkitCase sleuthKitCase;
|
||||||
// private NavPanel navPanel;
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
|
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
|
||||||
return metaDataCollapsed.getReadOnlyProperty();
|
return metaDataCollapsed.getReadOnlyProperty();
|
||||||
@ -175,7 +173,7 @@ public final class ImageGalleryController implements Executor {
|
|||||||
return historyManager.currentState();
|
return historyManager.currentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized FileIDSelectionModel getSelectionModel() {
|
public FileIDSelectionModel getSelectionModel() {
|
||||||
return selectionModel;
|
return selectionModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,12 +185,16 @@ public final class ImageGalleryController implements Executor {
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void setListeningEnabled(boolean enabled) {
|
public void setListeningEnabled(boolean enabled) {
|
||||||
listeningEnabled.set(enabled);
|
synchronized (listeningEnabled) {
|
||||||
|
listeningEnabled.set(enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isListeningEnabled() {
|
boolean isListeningEnabled() {
|
||||||
return listeningEnabled.get();
|
synchronized (listeningEnabled) {
|
||||||
|
return listeningEnabled.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||||
@ -248,12 +250,14 @@ public final class ImageGalleryController implements Executor {
|
|||||||
checkForGroups();
|
checkForGroups();
|
||||||
});
|
});
|
||||||
|
|
||||||
IngestManager.getInstance().addIngestModuleEventListener((PropertyChangeEvent evt) -> {
|
IngestManager ingestManager = IngestManager.getInstance();
|
||||||
Platform.runLater(this::updateRegroupDisabled);
|
PropertyChangeListener ingestEventHandler =
|
||||||
});
|
propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
|
||||||
IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {
|
|
||||||
Platform.runLater(this::updateRegroupDisabled);
|
ingestManager.addIngestModuleEventListener(ingestEventHandler);
|
||||||
});
|
ingestManager.addIngestJobEventListener(ingestEventHandler);
|
||||||
|
|
||||||
|
queueSizeProperty.addListener(obs -> this.updateRegroupDisabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty getCanAdvance() {
|
public ReadOnlyBooleanProperty getCanAdvance() {
|
||||||
@ -280,8 +284,9 @@ public final class ImageGalleryController implements Executor {
|
|||||||
return historyManager.retreat();
|
return historyManager.retreat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
private void updateRegroupDisabled() {
|
private void updateRegroupDisabled() {
|
||||||
regroupDisabled.set(getFileUpdateQueueSizeProperty().get() > 0 || IngestManager.getInstance().isIngestRunning());
|
regroupDisabled.set((queueSizeProperty.get() > 0) || IngestManager.getInstance().isIngestRunning());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -290,16 +295,16 @@ public final class ImageGalleryController implements Executor {
|
|||||||
* aren't, add a blocking progress spinner with appropriate message.
|
* aren't, add a blocking progress spinner with appropriate message.
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " +
|
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
|
||||||
" No groups will be available until ingest is finished and listening is re-enabled.",
|
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||||
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
|
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
|
||||||
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
|
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
|
||||||
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. " +
|
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. "
|
||||||
" No groups will be available until ingest is finished and listening is re-enabled.",
|
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||||
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
|
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
|
||||||
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" +
|
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
|
||||||
" the current Group By setting resulted in no groups, " +
|
+ " the current Group By setting resulted in no groups, "
|
||||||
"or no groups are fully analyzed but ingest is not running."})
|
+ "or no groups are fully analyzed but ingest is not running."})
|
||||||
public void checkForGroups() {
|
public void checkForGroups() {
|
||||||
if (groupManager.getAnalyzedGroups().isEmpty()) {
|
if (groupManager.getAnalyzedGroups().isEmpty()) {
|
||||||
if (IngestManager.getInstance().isIngestRunning()) {
|
if (IngestManager.getInstance().isIngestRunning()) {
|
||||||
@ -312,7 +317,7 @@ public final class ImageGalleryController implements Executor {
|
|||||||
new ProgressIndicator()));
|
new ProgressIndicator()));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (getFileUpdateQueueSizeProperty().get() > 0) {
|
} else if (queueSizeProperty.get() > 0) {
|
||||||
replaceNotification(fullUIStackPane,
|
replaceNotification(fullUIStackPane,
|
||||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
|
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
|
||||||
new ProgressIndicator()));
|
new ProgressIndicator()));
|
||||||
@ -357,20 +362,14 @@ public final class ImageGalleryController implements Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restartWorker() {
|
synchronized private DBWorkerThread restartWorker() {
|
||||||
if (dbWorkerThread != null) {
|
if (dbWorkerThread == null) {
|
||||||
|
dbWorkerThread = new DBWorkerThread(this);
|
||||||
|
dbWorkerThread.start();
|
||||||
|
} else {
|
||||||
// Keep using the same worker thread if one exists
|
// Keep using the same worker thread if one exists
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
dbWorkerThread = new DBWorkerThread();
|
return dbWorkerThread;
|
||||||
|
|
||||||
getFileUpdateQueueSizeProperty().addListener((Observable o) -> {
|
|
||||||
Platform.runLater(this::updateRegroupDisabled);
|
|
||||||
});
|
|
||||||
|
|
||||||
Thread th = new Thread(dbWorkerThread, "DB-Worker-Thread");
|
|
||||||
th.setDaemon(false); // we want it to go away when it is done
|
|
||||||
th.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -411,15 +410,16 @@ public final class ImageGalleryController implements Executor {
|
|||||||
setListeningEnabled(false);
|
setListeningEnabled(false);
|
||||||
ThumbnailCache.getDefault().clearCache();
|
ThumbnailCache.getDefault().clearCache();
|
||||||
historyManager.clear();
|
historyManager.clear();
|
||||||
|
groupManager.clear();
|
||||||
tagsManager.clearFollowUpTagName();
|
tagsManager.clearFollowUpTagName();
|
||||||
tagsManager.unregisterListener(groupManager);
|
tagsManager.unregisterListener(groupManager);
|
||||||
tagsManager.unregisterListener(categoryManager);
|
tagsManager.unregisterListener(categoryManager);
|
||||||
dbWorkerThread.cancelAllTasks();
|
dbWorkerThread.cancel();
|
||||||
dbWorkerThread = null;
|
dbWorkerThread = null;
|
||||||
restartWorker();
|
dbWorkerThread = restartWorker();
|
||||||
|
|
||||||
Toolbar.getDefault(this).reset();
|
Toolbar.getDefault(this).reset();
|
||||||
groupManager.clear();
|
|
||||||
if (db != null) {
|
if (db != null) {
|
||||||
db.closeDBCon();
|
db.closeDBCon();
|
||||||
}
|
}
|
||||||
@ -431,11 +431,9 @@ public final class ImageGalleryController implements Executor {
|
|||||||
*
|
*
|
||||||
* @param innerTask
|
* @param innerTask
|
||||||
*/
|
*/
|
||||||
public void queueDBWorkerTask(InnerTask innerTask) {
|
public synchronized void queueDBWorkerTask(BackgroundTask innerTask) {
|
||||||
|
|
||||||
// @@@ We could make a lock for the worker thread
|
|
||||||
if (dbWorkerThread == null) {
|
if (dbWorkerThread == null) {
|
||||||
restartWorker();
|
dbWorkerThread = restartWorker();
|
||||||
}
|
}
|
||||||
dbWorkerThread.addTask(innerTask);
|
dbWorkerThread.addTask(innerTask);
|
||||||
}
|
}
|
||||||
@ -455,10 +453,6 @@ public final class ImageGalleryController implements Executor {
|
|||||||
Platform.runLater(this::checkForGroups);
|
Platform.runLater(this::checkForGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() {
|
|
||||||
return queueSizeProperty.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyDoubleProperty regroupProgress() {
|
public ReadOnlyDoubleProperty regroupProgress() {
|
||||||
return groupManager.regroupProgress();
|
return groupManager.regroupProgress();
|
||||||
}
|
}
|
||||||
@ -496,29 +490,43 @@ public final class ImageGalleryController implements Executor {
|
|||||||
return undoManager;
|
return undoManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @@@ REVIEW IF THIS SHOLD BE STATIC...
|
public ReadOnlyIntegerProperty getDBTasksQueueSizeProperty() {
|
||||||
//TODO: concept seems like the controller deal with how much work to do at a given time
|
return queueSizeProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0);
|
||||||
|
|
||||||
// @@@ review this class for synchronization issues (i.e. reset and cancel being called, add, etc.)
|
// @@@ review this class for synchronization issues (i.e. reset and cancel being called, add, etc.)
|
||||||
private class DBWorkerThread implements Runnable {
|
static private class DBWorkerThread extends Thread implements Cancellable {
|
||||||
|
|
||||||
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
|
DBWorkerThread(ImageGalleryController controller) {
|
||||||
|
super("DB-Worker-Thread");
|
||||||
|
setDaemon(false);
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
// true if the process was requested to stop. Currently no way to reset it
|
// true if the process was requested to stop. Currently no way to reset it
|
||||||
private volatile boolean cancelled = false;
|
private volatile boolean cancelled = false;
|
||||||
|
|
||||||
// list of tasks to run
|
// list of tasks to run
|
||||||
private final BlockingQueue<InnerTask> workQueue = new LinkedBlockingQueue<>();
|
private final BlockingQueue<BackgroundTask> workQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel all of the queued up tasks and the currently scheduled task.
|
* Cancel all of the queued up tasks and the currently scheduled task.
|
||||||
* Note that after you cancel, you cannot submit new jobs to this
|
* Note that after you cancel, you cannot submit new jobs to this
|
||||||
* thread.
|
* thread.
|
||||||
*/
|
*/
|
||||||
public void cancelAllTasks() {
|
@Override
|
||||||
|
public boolean cancel() {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
for (InnerTask it : workQueue) {
|
for (BackgroundTask it : workQueue) {
|
||||||
it.cancel();
|
it.cancel();
|
||||||
}
|
}
|
||||||
workQueue.clear();
|
workQueue.clear();
|
||||||
queueSizeProperty.set(workQueue.size());
|
int size = workQueue.size();
|
||||||
|
Platform.runLater(() -> controller.queueSizeProperty.set(size));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -526,11 +534,10 @@ public final class ImageGalleryController implements Executor {
|
|||||||
*
|
*
|
||||||
* @param it
|
* @param it
|
||||||
*/
|
*/
|
||||||
public void addTask(InnerTask it) {
|
public void addTask(BackgroundTask it) {
|
||||||
workQueue.add(it);
|
workQueue.add(it);
|
||||||
Platform.runLater(() -> {
|
int size = workQueue.size();
|
||||||
queueSizeProperty.set(workQueue.size());
|
Platform.runLater(() -> controller.queueSizeProperty.set(size));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -538,19 +545,17 @@ public final class ImageGalleryController implements Executor {
|
|||||||
|
|
||||||
// nearly infinite loop waiting for tasks
|
// nearly infinite loop waiting for tasks
|
||||||
while (true) {
|
while (true) {
|
||||||
if (cancelled) {
|
if (cancelled || isInterrupted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
InnerTask it = workQueue.take();
|
BackgroundTask it = workQueue.take();
|
||||||
|
|
||||||
if (it.isCancelled() == false) {
|
if (it.isCancelled() == false) {
|
||||||
it.run();
|
it.run();
|
||||||
}
|
}
|
||||||
|
int size = workQueue.size();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> controller.queueSizeProperty.set(size));
|
||||||
queueSizeProperty.set(workQueue.size());
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Failed to run DB worker thread", ex); //NON-NLS
|
LOGGER.log(Level.SEVERE, "Failed to run DB worker thread", ex); //NON-NLS
|
||||||
@ -567,8 +572,15 @@ public final class ImageGalleryController implements Executor {
|
|||||||
* Abstract base class for task to be done on {@link DBWorkerThread}
|
* Abstract base class for task to be done on {@link DBWorkerThread}
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress",
|
@NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress",
|
||||||
"ImageGalleryController.InnerTask.message.name=status"})
|
"ImageGalleryController.InnerTask.message.name=status"})
|
||||||
static public abstract class InnerTask implements Runnable, Cancellable {
|
static public abstract class BackgroundTask implements Runnable, Cancellable {
|
||||||
|
|
||||||
|
private final SimpleObjectProperty<Worker.State> state = new SimpleObjectProperty<>(Worker.State.READY);
|
||||||
|
private final SimpleDoubleProperty progress = new SimpleDoubleProperty(this, Bundle.ImageGalleryController_InnerTask_progress_name());
|
||||||
|
private final SimpleStringProperty message = new SimpleStringProperty(this, Bundle.ImageGalleryController_InnerTask_message_name());
|
||||||
|
|
||||||
|
protected BackgroundTask() {
|
||||||
|
}
|
||||||
|
|
||||||
public double getProgress() {
|
public double getProgress() {
|
||||||
return progress.get();
|
return progress.get();
|
||||||
@ -585,9 +597,6 @@ public final class ImageGalleryController implements Executor {
|
|||||||
public final void updateMessage(String Status) {
|
public final void updateMessage(String Status) {
|
||||||
this.message.set(Status);
|
this.message.set(Status);
|
||||||
}
|
}
|
||||||
SimpleObjectProperty<Worker.State> state = new SimpleObjectProperty<>(Worker.State.READY);
|
|
||||||
SimpleDoubleProperty progress = new SimpleDoubleProperty(this, Bundle.ImageGalleryController_InnerTask_progress_name());
|
|
||||||
SimpleStringProperty message = new SimpleStringProperty(this, Bundle.ImageGalleryController_InnerTask_message_name());
|
|
||||||
|
|
||||||
public SimpleDoubleProperty progressProperty() {
|
public SimpleDoubleProperty progressProperty() {
|
||||||
return progress;
|
return progress;
|
||||||
@ -601,24 +610,21 @@ public final class ImageGalleryController implements Executor {
|
|||||||
return state.get();
|
return state.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateState(Worker.State newState) {
|
|
||||||
state.set(newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<Worker.State> stateProperty() {
|
public ReadOnlyObjectProperty<Worker.State> stateProperty() {
|
||||||
return new ReadOnlyObjectWrapper<>(state.get());
|
return new ReadOnlyObjectWrapper<>(state.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InnerTask() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
synchronized public boolean cancel() {
|
public synchronized boolean cancel() {
|
||||||
updateState(Worker.State.CANCELLED);
|
updateState(Worker.State.CANCELLED);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized protected boolean isCancelled() {
|
protected void updateState(Worker.State newState) {
|
||||||
|
state.set(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized boolean isCancelled() {
|
||||||
return getState() == Worker.State.CANCELLED;
|
return getState() == Worker.State.CANCELLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -626,7 +632,7 @@ public final class ImageGalleryController implements Executor {
|
|||||||
/**
|
/**
|
||||||
* Abstract base class for tasks associated with a file in the database
|
* Abstract base class for tasks associated with a file in the database
|
||||||
*/
|
*/
|
||||||
static public abstract class FileTask extends InnerTask {
|
static public abstract class FileTask extends BackgroundTask {
|
||||||
|
|
||||||
private final AbstractFile file;
|
private final AbstractFile file;
|
||||||
private final DrawableDB taskDB;
|
private final DrawableDB taskDB;
|
||||||
@ -644,7 +650,6 @@ public final class ImageGalleryController implements Executor {
|
|||||||
this.file = f;
|
this.file = f;
|
||||||
this.taskDB = taskDB;
|
this.taskDB = taskDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -701,63 +706,63 @@ public final class ImageGalleryController implements Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@NbBundle.Messages({"BulkTask.committingDb.status=commiting image/video database",
|
||||||
* Task that runs when image gallery listening is (re) enabled.
|
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
||||||
*
|
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||||
* Grabs all files with supported image/video mime types or extensions, and
|
abstract static private class BulkTransferTask extends BackgroundTask {
|
||||||
* adds them to the Drawable DB. Uses the presence of a mimetype as an
|
|
||||||
* approximation to 'analyzed'.
|
|
||||||
*/
|
|
||||||
@NbBundle.Messages({"CopyAnalyzedFiles.populatingDb.status=populating analyzed image/video database",
|
|
||||||
"CopyAnalyzedFiles.committingDb.status=commiting image/video database",
|
|
||||||
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
|
|
||||||
"CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
|
||||||
static private class CopyAnalyzedFiles extends InnerTask {
|
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
static private final String FILE_EXTENSION_CLAUSE =
|
||||||
private final DrawableDB taskDB;
|
"(name LIKE '%." //NON-NLS
|
||||||
private final SleuthkitCase tskCase;
|
+ String.join("' OR name LIKE '%.", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
|
||||||
|
+ "')";
|
||||||
|
|
||||||
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
static private final String MIMETYPE_CLAUSE =
|
||||||
|
"(mime_type LIKE '" //NON-NLS
|
||||||
|
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||||
|
+ "') ";
|
||||||
|
|
||||||
|
static final String DRAWABLE_QUERY =
|
||||||
|
//grab files with supported extension
|
||||||
|
"(" + FILE_EXTENSION_CLAUSE
|
||||||
|
//grab files with supported mime-types
|
||||||
|
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
|
||||||
|
//grab files with image or video mime-types even if we don't officially support them
|
||||||
|
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
|
||||||
|
|
||||||
|
final ImageGalleryController controller;
|
||||||
|
final DrawableDB taskDB;
|
||||||
|
final SleuthkitCase tskCase;
|
||||||
|
|
||||||
|
ProgressHandle progressHandle;
|
||||||
|
|
||||||
|
BulkTransferTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.taskDB = taskDB;
|
this.taskDB = taskDB;
|
||||||
this.tskCase = tskCase;
|
this.tskCase = tskCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
static private final String FILE_EXTENSION_CLAUSE =
|
abstract void cleanup(boolean success);
|
||||||
"(name LIKE '%." //NON-NLS
|
|
||||||
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") //NON-NLS
|
|
||||||
+ "')";
|
|
||||||
|
|
||||||
static private final String MIMETYPE_CLAUSE =
|
abstract List<AbstractFile> getFiles() throws TskCoreException;
|
||||||
"(mime_type LIKE '" //NON-NLS
|
|
||||||
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(), "' OR mime_type LIKE '") //NON-NLS
|
|
||||||
+ "') ";
|
|
||||||
|
|
||||||
static private final String DRAWABLE_QUERY =
|
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException;
|
||||||
//grab files with supported extension
|
|
||||||
FILE_EXTENSION_CLAUSE
|
|
||||||
//grab files with supported mime-types
|
|
||||||
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
|
|
||||||
//grab files with image or video mime-types even if we don't officially support them
|
|
||||||
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%'"; //NON-NLS
|
|
||||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
progressHandle = getInitialProgressHandle();
|
||||||
progressHandle.start();
|
progressHandle.start();
|
||||||
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//grab all files with supported extension or detected mime types
|
//grab all files with supported extension or detected mime types
|
||||||
final List<AbstractFile> files = tskCase.findAllFilesWhere(DRAWABLE_QUERY);
|
final List<AbstractFile> files = getFiles();
|
||||||
progressHandle.switchToDeterminate(files.size());
|
progressHandle.switchToDeterminate(files.size());
|
||||||
|
|
||||||
updateProgress(0.0);
|
updateProgress(0.0);
|
||||||
|
|
||||||
//do in transaction
|
//do in transaction
|
||||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
||||||
int units = 0;
|
int workDone = 0;
|
||||||
for (final AbstractFile f : files) {
|
for (final AbstractFile f : files) {
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS
|
LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS
|
||||||
@ -765,161 +770,172 @@ public final class ImageGalleryController implements Executor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
|
processFile(f, tr);
|
||||||
|
|
||||||
if (known) {
|
workDone++;
|
||||||
taskDB.removeFile(f.getId(), tr); //remove known files
|
progressHandle.progress(f.getName(), workDone);
|
||||||
} else {
|
updateProgress(workDone - 1 / (double) files.size());
|
||||||
Optional<String> mimeType = FileTypeUtils.getMimeType(f);
|
|
||||||
if (mimeType.isPresent()) {
|
|
||||||
//mime type
|
|
||||||
if (FileTypeUtils.isDrawableMimeType(mimeType.get())) { //supported mimetype => analyzed
|
|
||||||
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
|
|
||||||
} else { //unsupported mimtype => analyzed but shouldn't include
|
|
||||||
taskDB.removeFile(f.getId(), tr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//no mime tyoe
|
|
||||||
if (FileTypeUtils.isDrawable(f)) {
|
|
||||||
//no mime type but supported => add as not analyzed
|
|
||||||
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
|
|
||||||
} else {
|
|
||||||
//no mime type, not supported => remove ( should never get here)
|
|
||||||
taskDB.removeFile(f.getId(), tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
units++;
|
|
||||||
final int prog = units;
|
|
||||||
progressHandle.progress(f.getName(), units);
|
|
||||||
updateProgress(prog - 1 / (double) files.size());
|
|
||||||
updateMessage(f.getName());
|
updateMessage(f.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
|
progressHandle = ProgressHandleFactory.createHandle(Bundle.BulkTask_committingDb_status());
|
||||||
progressHandle = ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_committingDb_status());
|
updateMessage(Bundle.BulkTask_committingDb_status());
|
||||||
updateMessage(Bundle.CopyAnalyzedFiles_committingDb_status());
|
|
||||||
updateProgress(1.0);
|
updateProgress(1.0);
|
||||||
|
|
||||||
progressHandle.start();
|
progressHandle.start();
|
||||||
taskDB.commitTransaction(tr, true);
|
taskDB.commitTransaction(tr, true);
|
||||||
|
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
progressHandle.progress(Bundle.CopyAnalyzedFiles_stopCopy_status());
|
progressHandle.progress(Bundle.BulkTask_stopCopy_status());
|
||||||
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents: " + ex.getMessage()); //NON-NLS
|
LOGGER.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
|
||||||
MessageNotifyUtil.Notify.warn(Bundle.CopyAnalyzedFiles_errPopulating_errMsg(), ex.getMessage());
|
MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
|
||||||
|
cleanup(false);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
updateMessage("");
|
updateMessage("");
|
||||||
updateProgress(-1.0);
|
updateProgress(-1.0);
|
||||||
controller.setStale(true);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cleanup(true);
|
||||||
|
}
|
||||||
|
|
||||||
progressHandle.finish();
|
abstract ProgressHandle getInitialProgressHandle();
|
||||||
updateMessage("");
|
}
|
||||||
updateProgress(-1.0);
|
|
||||||
controller.setStale(false);
|
/**
|
||||||
|
* Task that runs when image gallery listening is (re) enabled.
|
||||||
|
*
|
||||||
|
* Grabs all files with supported image/video mime types or extensions, and
|
||||||
|
* adds them to the Drawable DB. Uses the presence of a mimetype as an
|
||||||
|
* approximation to 'analyzed'.
|
||||||
|
*/
|
||||||
|
@NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=commiting image/video database",
|
||||||
|
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
|
||||||
|
"CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||||
|
static private class CopyAnalyzedFiles extends BulkTransferTask {
|
||||||
|
|
||||||
|
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||||
|
super(controller, taskDB, tskCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup(boolean success) {
|
||||||
|
controller.setStale(!success);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
List<AbstractFile> getFiles() throws TskCoreException {
|
||||||
|
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException {
|
||||||
|
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
|
||||||
|
|
||||||
|
if (known) {
|
||||||
|
taskDB.removeFile(f.getId(), tr); //remove known files
|
||||||
|
} else {
|
||||||
|
Optional<String> mimeType = FileTypeUtils.getMimeType(f);
|
||||||
|
if (mimeType.isPresent()) {
|
||||||
|
//mime type
|
||||||
|
if (FileTypeUtils.isDrawableMimeType(mimeType.get())) { //supported mimetype => analyzed
|
||||||
|
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
|
||||||
|
} else { //unsupported mimtype => analyzed but shouldn't include
|
||||||
|
taskDB.removeFile(f.getId(), tr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//no mime tyoe
|
||||||
|
if (FileTypeUtils.isDrawable(f)) {
|
||||||
|
//no mime type but supported => add as not analyzed
|
||||||
|
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
|
||||||
|
} else {
|
||||||
|
//no mime type, not supported => remove ( should never get here)
|
||||||
|
taskDB.removeFile(f.getId(), tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages({"CopyAnalyzedFiles.populatingDb.status=populating analyzed image/video database",})
|
||||||
|
ProgressHandle getInitialProgressHandle() {
|
||||||
|
return ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_populatingDb_status(), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* task that does pre-ingest copy over of files from a new datasource (uses
|
* Copy files from a newly added data source into the DB. Get all "drawable"
|
||||||
* fs_obj_id to identify files from new datasources)
|
* files, based on extension and mime-type. After ingest we use file type id
|
||||||
|
* module and if necessary jpeg/png signature matching to add/remove files
|
||||||
*
|
*
|
||||||
* TODO: create methods to simplify progress value/text updates to both
|
* TODO: create methods to simplify progress value/text updates to both
|
||||||
* netbeans and ImageGallery progress/status
|
* netbeans and ImageGallery progress/status
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",
|
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"})
|
||||||
"PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"})
|
static private class PrePopulateDataSourceFiles extends BulkTransferTask {
|
||||||
private class PrePopulateDataSourceFiles extends InnerTask {
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName());
|
||||||
|
|
||||||
private final Content dataSource;
|
private final Content dataSource;
|
||||||
|
|
||||||
/**
|
|
||||||
* here we grab by extension but in file_done listener we look at file
|
|
||||||
* type id attributes but fall back on jpeg signatures and extensions to
|
|
||||||
* check for supported images
|
|
||||||
*/
|
|
||||||
// (name like '.jpg' or name like '.png' ...)
|
|
||||||
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') "; //NON-NLS
|
|
||||||
|
|
||||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param dataSourceId Data source object ID
|
* @param dataSourceId Data source object ID
|
||||||
*/
|
*/
|
||||||
PrePopulateDataSourceFiles(Content dataSource) {
|
PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||||
super();
|
super(controller, taskDB, tskCase);
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy files from a newly added data source into the DB. Get all
|
|
||||||
* "drawable" files, based on extension. After ingest we use file type
|
|
||||||
* id module and if necessary jpeg/png signature matching to add/remove
|
|
||||||
* files
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
protected void cleanup(boolean success) {
|
||||||
progressHandle.start();
|
}
|
||||||
updateMessage(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status());
|
|
||||||
|
|
||||||
try {
|
@Override
|
||||||
String fsQuery = "(fs_obj_id IS NULL) "; //default clause NON-NLS
|
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) {
|
||||||
|
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
List<AbstractFile> getFiles() throws TskCoreException {
|
||||||
|
if (dataSource instanceof Image) {
|
||||||
|
List<FileSystem> fileSystems = ((Image) dataSource).getFileSystems();
|
||||||
|
if (fileSystems.isEmpty()) {
|
||||||
|
/*
|
||||||
|
* no filesystems, don't bother with the initial population,
|
||||||
|
* just sort things out on file_done events
|
||||||
|
*/
|
||||||
|
progressHandle.finish();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
//use this clause to only grab files from the newly added filesystems.
|
||||||
|
String fsQuery = fileSystems.stream()
|
||||||
|
.map(fileSystem -> String.valueOf(fileSystem.getId()))
|
||||||
|
.collect(Collectors.joining(" OR fs_obj_id = ", "(fs_obj_id = ", ") ")); //NON-NLS
|
||||||
|
|
||||||
|
return tskCase.findAllFilesWhere(fsQuery + " AND " + DRAWABLE_QUERY); //NON-NLS
|
||||||
|
} else if (dataSource instanceof VirtualDirectory) {
|
||||||
/*
|
/*
|
||||||
* NOTE: Logical files currently (Apr '15) have a null value for
|
* fs_obj_id is set only for file system files, so we will match
|
||||||
* fs_obj_id in DB. for them, we will not specify a fs_obj_id,
|
* the VirtualDirectory's name in the parent path.
|
||||||
* which means we will grab files from another data source, but
|
*
|
||||||
* the drawable DB is smart enough to de-dupe them. For Images
|
* TODO: A future database schema could probably make this
|
||||||
* we can do better.
|
* cleaner. If we had a datasource_id column in the files table
|
||||||
|
* we could just match agains that.
|
||||||
*/
|
*/
|
||||||
if (dataSource instanceof Image) {
|
return tskCase.findAllFilesWhere(" parent_path LIKE '/" + dataSource.getName() + "/%' AND " + DRAWABLE_QUERY); //NON-NLS
|
||||||
List<FileSystem> fileSystems = ((Image) dataSource).getFileSystems();
|
} else {
|
||||||
if (fileSystems.isEmpty()) {
|
String msg = "Uknown datasource type: " + dataSource.getClass().getName();
|
||||||
/*
|
LOGGER.log(Level.SEVERE, msg);
|
||||||
* no filesystems, don't bother with the initial
|
throw new IllegalArgumentException(msg);
|
||||||
* population, just sort things out on file_done events
|
|
||||||
*/
|
|
||||||
progressHandle.finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//use this clause to only grab files from the newly added filesystems.
|
|
||||||
fsQuery = fileSystems.stream()
|
|
||||||
.map(fileSystem -> String.valueOf(fileSystem.getId()))
|
|
||||||
.collect(Collectors.joining(" OR fs_obj_id = ", "(fs_obj_id = ", ") ")); //NON-NLS
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<AbstractFile> files = getSleuthKitCase().findAllFilesWhere(fsQuery + " AND " + DRAWABLE_QUERY); //NON-NLS
|
|
||||||
progressHandle.switchToDeterminate(files.size());
|
|
||||||
|
|
||||||
//do in transaction
|
|
||||||
DrawableDB.DrawableTransaction tr = db.beginTransaction();
|
|
||||||
int units = 0;
|
|
||||||
for (final AbstractFile f : files) {
|
|
||||||
if (isCancelled()) {
|
|
||||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database"); //NON-NLS
|
|
||||||
progressHandle.finish();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
db.insertFile(DrawableFile.create(f, false, false), tr);
|
|
||||||
units++;
|
|
||||||
progressHandle.progress(f.getName(), units);
|
|
||||||
}
|
|
||||||
|
|
||||||
progressHandle.finish();
|
|
||||||
progressHandle = ProgressHandleFactory.createHandle(Bundle.PrePopulateDataSourceFiles_committingDb_status());
|
|
||||||
|
|
||||||
progressHandle.start();
|
|
||||||
db.commitTransaction(tr, false);
|
|
||||||
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex); //NON-NLS
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
progressHandle.finish();
|
@Override
|
||||||
|
@NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",})
|
||||||
|
ProgressHandle getInitialProgressHandle() {
|
||||||
|
return ProgressHandleFactory.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1011,7 +1027,7 @@ public final class ImageGalleryController implements Executor {
|
|||||||
//copy all file data to drawable databse
|
//copy all file data to drawable databse
|
||||||
Content newDataSource = (Content) evt.getNewValue();
|
Content newDataSource = (Content) evt.getNewValue();
|
||||||
if (isListeningEnabled()) {
|
if (isListeningEnabled()) {
|
||||||
queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource));
|
queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase()));
|
||||||
} else {//TODO: keep track of what we missed for later
|
} else {//TODO: keep track of what we missed for later
|
||||||
setStale(true);
|
setStale(true);
|
||||||
}
|
}
|
||||||
@ -1028,7 +1044,6 @@ public final class ImageGalleryController implements Executor {
|
|||||||
getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
|
getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,14 +157,15 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
hashHitList = new HashHitGroupList(controller);
|
hashHitList = new HashHitGroupList(controller);
|
||||||
|
|
||||||
TabPane tabPane = new TabPane(groupTree, hashHitList);
|
TabPane tabPane = new TabPane(groupTree, hashHitList);
|
||||||
|
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
|
||||||
|
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
|
||||||
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
||||||
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
|
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
|
||||||
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
|
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
|
||||||
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
|
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
|
||||||
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
|
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
|
||||||
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
|
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
|
||||||
splitPane.setDividerPositions(0.0, 1.0);
|
splitPane.setDividerPositions(0.1, 1.0);
|
||||||
|
|
||||||
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
|
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
|
||||||
ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree));
|
ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree));
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* Autopsy Forensic Browser
|
|
||||||
*
|
|
||||||
* Copyright 2013-15 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.actions;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Menu;
|
|
||||||
import javax.swing.SwingWorker;
|
|
||||||
|
|
||||||
import org.openide.util.NbBundle;
|
|
||||||
import org.openide.util.Utilities;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
|
||||||
import org.sleuthkit.datamodel.TagName;
|
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instances of this Action allow users to apply tags to content.
|
|
||||||
*
|
|
||||||
* //TODO: since we are not using actionsGlobalContext anymore and this has
|
|
||||||
* diverged from autopsy action, make this extend from controlsfx Action
|
|
||||||
*/
|
|
||||||
public class AddDrawableTagAction extends AddTagAction {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(AddDrawableTagAction.class.getName());
|
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
|
||||||
|
|
||||||
public AddDrawableTagAction(ImageGalleryController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Menu getPopupMenu() {
|
|
||||||
return new TagMenu(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NbBundle.Messages({"AddDrawableTagAction.displayName.plural=Tag Files",
|
|
||||||
"AddDrawableTagAction.displayName.singular=Tag File"})
|
|
||||||
protected String getActionDisplayName() {
|
|
||||||
return Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size() > 1
|
|
||||||
? Bundle.AddDrawableTagAction_displayName_plural()
|
|
||||||
: Bundle.AddDrawableTagAction_displayName_singular();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTag(TagName tagName, String comment) {
|
|
||||||
Set<Long> selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected());
|
|
||||||
addTagsToFiles(tagName, comment, selectedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NbBundle.Messages({"# {0} - fileID",
|
|
||||||
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
|
|
||||||
public void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
|
|
||||||
new SwingWorker<Void, Void>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground() throws Exception {
|
|
||||||
for (Long fileID : selectedFiles) {
|
|
||||||
try {
|
|
||||||
final DrawableFile<?> file = controller.getFileFromId(fileID);
|
|
||||||
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS
|
|
||||||
|
|
||||||
// check if the same tag is being added for the same abstract file.
|
|
||||||
DrawableTagsManager tagsManager = controller.getTagsManager();
|
|
||||||
List<ContentTag> contentTags = tagsManager.getContentTagsByContent(file);
|
|
||||||
Optional<TagName> duplicateTagName = contentTags.stream()
|
|
||||||
.map(ContentTag::getName)
|
|
||||||
.filter(tagName::equals)
|
|
||||||
.findAny();
|
|
||||||
|
|
||||||
if (duplicateTagName.isPresent()) {
|
|
||||||
LOGGER.log(Level.INFO, "{0} already tagged as {1}. Skipping.", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
|
|
||||||
} else {
|
|
||||||
LOGGER.log(Level.INFO, "Tagging {0} as {1}", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
|
|
||||||
controller.getTagsManager().addContentTag(file, tagName, comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (TskCoreException tskCoreException) {
|
|
||||||
LOGGER.log(Level.SEVERE, "Error tagging file", tskCoreException); //NON-NLS
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
new Alert(Alert.AlertType.ERROR, Bundle.AddDrawableTagAction_addTagsToFiles_alert(fileID)).show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
super.done();
|
|
||||||
try {
|
|
||||||
get();
|
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
|
||||||
LOGGER.log(Level.SEVERE, "unexpected exception while tagging files", ex); //NON-NLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-16 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -20,67 +20,128 @@ package org.sleuthkit.autopsy.imagegallery.actions;
|
|||||||
|
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javafx.event.ActionEvent;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.ObservableSet;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Menu;
|
import javafx.scene.control.Menu;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.windows.TopComponent;
|
import org.openide.windows.TopComponent;
|
||||||
import org.openide.windows.WindowManager;
|
import org.openide.windows.WindowManager;
|
||||||
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
|
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
|
||||||
import org.sleuthkit.autopsy.actions.GetTagNameDialog;
|
import org.sleuthkit.autopsy.actions.GetTagNameDialog;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||||
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract base class for actions that allow users to tag SleuthKit data
|
* Instances of this Action allow users to apply tags to content.
|
||||||
* model objects.
|
|
||||||
*
|
|
||||||
* //TODO: this class started as a cut and paste from
|
|
||||||
* org.sleuthkit.autopsy.actions.AddTagAction and needs to be refactored or
|
|
||||||
* reintegrated to the AddTagAction hierarchy of Autopysy.
|
|
||||||
*/
|
*/
|
||||||
abstract class AddTagAction {
|
public class AddTagAction extends Action {
|
||||||
|
|
||||||
protected static final String NO_COMMENT = "";
|
private static final Logger LOGGER = Logger.getLogger(AddTagAction.class.getName());
|
||||||
|
|
||||||
/**
|
private final ImageGalleryController controller;
|
||||||
* Template method to allow derived classes to provide a string for a menu
|
private final Set<Long> selectedFileIDs;
|
||||||
* item label.
|
private final TagName tagName;
|
||||||
*/
|
|
||||||
abstract protected String getActionDisplayName();
|
|
||||||
|
|
||||||
/**
|
public AddTagAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs) {
|
||||||
* Template method to allow derived classes to add the indicated tag and
|
super(tagName.getDisplayName());
|
||||||
* comment to one or more a SleuthKit data model objects.
|
this.controller = controller;
|
||||||
*/
|
this.selectedFileIDs = selectedFileIDs;
|
||||||
abstract protected void addTag(TagName tagName, String comment);
|
this.tagName = tagName;
|
||||||
|
setGraphic(controller.getTagsManager().getGraphic(tagName));
|
||||||
|
setText(tagName.getDisplayName());
|
||||||
|
setEventHandler(actionEvent -> addTagWithComment(""));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
static public Menu getTagMenu(ImageGalleryController controller) {
|
||||||
* Template method to allow derived classes to add the indicated tag and
|
return new TagMenu(controller);
|
||||||
* comment to a list of one or more file IDs.
|
}
|
||||||
*/
|
|
||||||
abstract protected void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles);
|
private void addTagWithComment(String comment) {
|
||||||
|
addTagsToFiles(tagName, comment, selectedFileIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({"# {0} - fileID",
|
||||||
|
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
|
||||||
|
private void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
|
||||||
|
new SwingWorker<Void, Void>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Exception {
|
||||||
|
// check if the same tag is being added for the same abstract file.
|
||||||
|
DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||||
|
for (Long fileID : selectedFiles) {
|
||||||
|
try {
|
||||||
|
final DrawableFile<?> file = controller.getFileFromId(fileID);
|
||||||
|
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS
|
||||||
|
|
||||||
|
List<ContentTag> contentTags = tagsManager.getContentTagsByContent(file);
|
||||||
|
Optional<TagName> duplicateTagName = contentTags.stream()
|
||||||
|
.map(ContentTag::getName)
|
||||||
|
.filter(tagName::equals)
|
||||||
|
.findAny();
|
||||||
|
|
||||||
|
if (duplicateTagName.isPresent()) {
|
||||||
|
LOGGER.log(Level.INFO, "{0} already tagged as {1}. Skipping.", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
|
||||||
|
} else {
|
||||||
|
LOGGER.log(Level.INFO, "Tagging {0} as {1}", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
|
||||||
|
controller.getTagsManager().addContentTag(file, tagName, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (TskCoreException tskCoreException) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Error tagging file", tskCoreException); //NON-NLS
|
||||||
|
Platform.runLater(() ->
|
||||||
|
new Alert(Alert.AlertType.ERROR, Bundle.AddDrawableTagAction_addTagsToFiles_alert(fileID)).show()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
super.done();
|
||||||
|
try {
|
||||||
|
get();
|
||||||
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "unexpected exception while tagging files", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Instances of this class implement a context menu user interface for
|
|
||||||
* creating or selecting a tag name for a tag and specifying an optional tag
|
|
||||||
* comment.
|
|
||||||
*/
|
|
||||||
// @@@ This user interface has some significant usability issues and needs
|
|
||||||
// to be reworked.
|
|
||||||
@NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag",
|
@NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag",
|
||||||
"AddTagAction.menuItem.noTags=No tags",
|
"AddTagAction.menuItem.noTags=No tags",
|
||||||
"AddTagAction.menuItem.newTag=New Tag...",
|
"AddTagAction.menuItem.newTag=New Tag...",
|
||||||
"AddTagAction.menuItem.tagAndComment=Tag and Comment..."})
|
"AddTagAction.menuItem.tagAndComment=Tag and Comment...",
|
||||||
protected class TagMenu extends Menu {
|
"AddDrawableTagAction.displayName.plural=Tag Files",
|
||||||
|
"AddDrawableTagAction.displayName.singular=Tag File"})
|
||||||
|
private static class TagMenu extends Menu {
|
||||||
|
|
||||||
TagMenu(ImageGalleryController controller) {
|
TagMenu(ImageGalleryController controller) {
|
||||||
super(getActionDisplayName());
|
setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon()));
|
||||||
|
ObservableSet<Long> selectedFileIDs = controller.getSelectionModel().getSelected();
|
||||||
|
setText(selectedFileIDs.size() > 1
|
||||||
|
? Bundle.AddDrawableTagAction_displayName_plural()
|
||||||
|
: Bundle.AddDrawableTagAction_displayName_singular());
|
||||||
|
|
||||||
// Create a "Quick Tag" sub-menu.
|
// Create a "Quick Tag" sub-menu.
|
||||||
Menu quickTagMenu = new Menu(Bundle.AddTagAction_menuItem_quickTag());
|
Menu quickTagMenu = new Menu(Bundle.AddTagAction_menuItem_quickTag());
|
||||||
@ -98,10 +159,8 @@ abstract class AddTagAction {
|
|||||||
quickTagMenu.getItems().add(empty);
|
quickTagMenu.getItems().add(empty);
|
||||||
} else {
|
} else {
|
||||||
for (final TagName tagName : tagNames) {
|
for (final TagName tagName : tagNames) {
|
||||||
MenuItem tagNameItem = new MenuItem(tagName.getDisplayName());
|
AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs);
|
||||||
tagNameItem.setOnAction((ActionEvent t) -> {
|
MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction);
|
||||||
addTag(tagName, NO_COMMENT);
|
|
||||||
});
|
|
||||||
quickTagMenu.getItems().add(tagNameItem);
|
quickTagMenu.getItems().add(tagNameItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,14 +171,13 @@ abstract class AddTagAction {
|
|||||||
* or select a tag name and adds a tag with the resulting name.
|
* or select a tag name and adds a tag with the resulting name.
|
||||||
*/
|
*/
|
||||||
MenuItem newTagMenuItem = new MenuItem(Bundle.AddTagAction_menuItem_newTag());
|
MenuItem newTagMenuItem = new MenuItem(Bundle.AddTagAction_menuItem_newTag());
|
||||||
newTagMenuItem.setOnAction((ActionEvent t) -> {
|
newTagMenuItem.setOnAction(actionEvent ->
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
TagName tagName = GetTagNameDialog.doDialog(getIGWindow());
|
TagName tagName = GetTagNameDialog.doDialog(getIGWindow());
|
||||||
if (tagName != null) {
|
if (tagName != null) {
|
||||||
addTag(tagName, NO_COMMENT);
|
new AddTagAction(controller, tagName, selectedFileIDs).handle(actionEvent);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
|
||||||
quickTagMenu.getItems().add(newTagMenuItem);
|
quickTagMenu.getItems().add(newTagMenuItem);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -129,26 +187,17 @@ abstract class AddTagAction {
|
|||||||
* name.
|
* name.
|
||||||
*/
|
*/
|
||||||
MenuItem tagAndCommentItem = new MenuItem(Bundle.AddTagAction_menuItem_tagAndComment());
|
MenuItem tagAndCommentItem = new MenuItem(Bundle.AddTagAction_menuItem_tagAndComment());
|
||||||
tagAndCommentItem.setOnAction((ActionEvent t) -> {
|
tagAndCommentItem.setOnAction(actionEvent ->
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow());
|
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow());
|
||||||
if (null != tagNameAndComment) {
|
if (null != tagNameAndComment) {
|
||||||
if (CategoryManager.isCategoryTagName(tagNameAndComment.getTagName())) {
|
new AddTagAction(controller, tagNameAndComment.getTagName(), selectedFileIDs).addTagWithComment(tagNameAndComment.getComment());
|
||||||
new CategorizeAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
|
|
||||||
} else {
|
|
||||||
new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
|
|
||||||
}
|
}
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
getItems().add(tagAndCommentItem);
|
getItems().add(tagAndCommentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the Window containing the ImageGalleryTopComponent
|
|
||||||
*/
|
|
||||||
static private Window getIGWindow() {
|
static private Window getIGWindow() {
|
||||||
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
||||||
return SwingUtilities.getWindowAncestor(etc);
|
return SwingUtilities.getWindowAncestor(etc);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-16 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -21,26 +21,28 @@ package org.sleuthkit.autopsy.imagegallery.actions;
|
|||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.collections.ObservableSet;
|
||||||
import javafx.scene.control.Menu;
|
import javafx.scene.control.Menu;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyCodeCombination;
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
@ -49,48 +51,42 @@ import org.sleuthkit.datamodel.TagName;
|
|||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adaptation of Tag Actions to enforce category-tag uniqueness
|
|
||||||
*
|
*
|
||||||
* TODO: since we are not using actionsGlobalContext anymore and this has
|
|
||||||
* diverged from autopsy action, make this extend from controlsfx Action
|
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"CategorizeAction.displayName=Categorize"})
|
@NbBundle.Messages({"CategorizeAction.displayName=Categorize"})
|
||||||
public class CategorizeAction extends AddTagAction {
|
public class CategorizeAction extends Action {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName());
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
private final UndoRedoManager undoManager;
|
private final UndoRedoManager undoManager;
|
||||||
|
private final Category cat;
|
||||||
|
private final Set<Long> selectedFileIDs;
|
||||||
|
private final Boolean createUndo;
|
||||||
|
|
||||||
public CategorizeAction(ImageGalleryController controller) {
|
public CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs) {
|
||||||
super();
|
this(controller, cat, selectedFileIDs, true);
|
||||||
this.controller = controller;
|
|
||||||
undoManager = controller.getUndoManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Menu getPopupMenu() {
|
private CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs, Boolean createUndo) {
|
||||||
|
super(cat.getDisplayName());
|
||||||
|
this.controller = controller;
|
||||||
|
this.undoManager = controller.getUndoManager();
|
||||||
|
this.cat = cat;
|
||||||
|
this.selectedFileIDs = selectedFileIDs;
|
||||||
|
this.createUndo = createUndo;
|
||||||
|
setGraphic(cat.getGraphic());
|
||||||
|
setEventHandler(actionEvent -> addCatToFiles());
|
||||||
|
setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Menu getCategoriesMenu(ImageGalleryController controller) {
|
||||||
return new CategoryMenu(controller);
|
return new CategoryMenu(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void addCatToFiles() {
|
||||||
protected String getActionDisplayName() {
|
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFileIDs.toString(), cat.getDisplayName()}); //NON-NLS
|
||||||
return Bundle.CategorizeAction_displayName();
|
controller.queueDBWorkerTask(new CategorizeTask(selectedFileIDs, cat, createUndo));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTag(TagName tagName, String comment) {
|
|
||||||
Set<Long> selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected());
|
|
||||||
addTagsToFiles(tagName, comment, selectedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
|
|
||||||
addTagsToFiles(tagName, comment, selectedFiles, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles, boolean createUndo) {
|
|
||||||
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFiles.toString(), tagName.getDisplayName()}); //NON-NLS
|
|
||||||
controller.queueDBWorkerTask(new CategorizeTask(selectedFiles, tagName, comment, createUndo));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,49 +97,43 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
|
|
||||||
CategoryMenu(ImageGalleryController controller) {
|
CategoryMenu(ImageGalleryController controller) {
|
||||||
super(Bundle.CategorizeAction_displayName());
|
super(Bundle.CategorizeAction_displayName());
|
||||||
|
setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon()));
|
||||||
|
ObservableSet<Long> selected = controller.getSelectionModel().getSelected();
|
||||||
|
|
||||||
// Each category get an item in the sub-menu. Selecting one of these menu items adds
|
// Each category get an item in the sub-menu. Selecting one of these menu items adds
|
||||||
// a tag with the associated category.
|
// a tag with the associated category.
|
||||||
for (final Category cat : Category.values()) {
|
for (final Category cat : Category.values()) {
|
||||||
|
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected));
|
||||||
MenuItem categoryItem = new MenuItem(cat.getDisplayName());
|
|
||||||
categoryItem.setOnAction((ActionEvent t) -> {
|
|
||||||
final CategorizeAction categorizeAction = new CategorizeAction(controller);
|
|
||||||
categorizeAction.addTag(controller.getCategoryManager().getTagName(cat), NO_COMMENT);
|
|
||||||
});
|
|
||||||
categoryItem.setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
|
|
||||||
getItems().add(categoryItem);
|
getItems().add(categoryItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages({"# {0} - fileID number",
|
@NbBundle.Messages({"# {0} - fileID number",
|
||||||
"CategorizeTask.errorUnable.msg=Unable to categorize {0}.",
|
"CategorizeTask.errorUnable.msg=Unable to categorize {0}.",
|
||||||
"CategorizeTask.errorUnable.title=Categorizing Error"})
|
"CategorizeTask.errorUnable.title=Categorizing Error"})
|
||||||
private class CategorizeTask extends ImageGalleryController.InnerTask {
|
private class CategorizeTask extends ImageGalleryController.BackgroundTask {
|
||||||
|
|
||||||
private final Set<Long> fileIDs;
|
private final Set<Long> fileIDs;
|
||||||
@Nonnull
|
|
||||||
private final TagName tagName;
|
|
||||||
private final String comment;
|
|
||||||
private final boolean createUndo;
|
|
||||||
|
|
||||||
CategorizeTask(Set<Long> fileIDs, @Nonnull TagName tagName, String comment, boolean createUndo) {
|
private final boolean createUndo;
|
||||||
|
private final Category cat;
|
||||||
|
|
||||||
|
CategorizeTask(Set<Long> fileIDs, @Nonnull Category cat, boolean createUndo) {
|
||||||
super();
|
super();
|
||||||
this.fileIDs = fileIDs;
|
this.fileIDs = fileIDs;
|
||||||
java.util.Objects.requireNonNull(tagName);
|
java.util.Objects.requireNonNull(cat);
|
||||||
this.tagName = tagName;
|
this.cat = cat;
|
||||||
this.comment = comment;
|
|
||||||
this.createUndo = createUndo;
|
this.createUndo = createUndo;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||||
final CategoryManager categoryManager = controller.getCategoryManager();
|
final CategoryManager categoryManager = controller.getCategoryManager();
|
||||||
Map<Long, TagName> oldCats = new HashMap<>();
|
Map<Long, Category> oldCats = new HashMap<>();
|
||||||
|
TagName tagName = categoryManager.getTagName(cat);
|
||||||
|
TagName catZeroTagName = categoryManager.getTagName(Category.ZERO);
|
||||||
for (long fileID : fileIDs) {
|
for (long fileID : fileIDs) {
|
||||||
try {
|
try {
|
||||||
DrawableFile<?> file = controller.getFileFromId(fileID); //drawable db access
|
DrawableFile<?> file = controller.getFileFromId(fileID); //drawable db access
|
||||||
@ -151,12 +141,12 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
Category oldCat = file.getCategory(); //drawable db access
|
Category oldCat = file.getCategory(); //drawable db access
|
||||||
TagName oldCatTagName = categoryManager.getTagName(oldCat);
|
TagName oldCatTagName = categoryManager.getTagName(oldCat);
|
||||||
if (false == tagName.equals(oldCatTagName)) {
|
if (false == tagName.equals(oldCatTagName)) {
|
||||||
oldCats.put(fileID, oldCatTagName);
|
oldCats.put(fileID, oldCat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<ContentTag> fileTags = tagsManager.getContentTagsByContent(file);
|
final List<ContentTag> fileTags = tagsManager.getContentTagsByContent(file);
|
||||||
if (tagName == categoryManager.getTagName(Category.ZERO)) {
|
if (tagName.equals(catZeroTagName)) {
|
||||||
// delete all cat tags for cat-0
|
// delete all cat tags for cat-0
|
||||||
fileTags.stream()
|
fileTags.stream()
|
||||||
.filter(tag -> CategoryManager.isCategoryTagName(tag.getName()))
|
.filter(tag -> CategoryManager.isCategoryTagName(tag.getName()))
|
||||||
@ -173,7 +163,7 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
.map(Tag::getName)
|
.map(Tag::getName)
|
||||||
.filter(tagName::equals)
|
.filter(tagName::equals)
|
||||||
.collect(Collectors.toList()).isEmpty()) {
|
.collect(Collectors.toList()).isEmpty()) {
|
||||||
tagsManager.addContentTag(file, tagName, comment);
|
tagsManager.addContentTag(file, tagName, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
@ -186,7 +176,7 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (createUndo && oldCats.isEmpty() == false) {
|
if (createUndo && oldCats.isEmpty() == false) {
|
||||||
undoManager.addToUndo(new CategorizationChange(controller, tagName, oldCats));
|
undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,11 +187,11 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
@Immutable
|
@Immutable
|
||||||
private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
|
private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
|
||||||
|
|
||||||
private final TagName newCategory;
|
private final Category newCategory;
|
||||||
private final ImmutableMap<Long, TagName> oldCategories;
|
private final ImmutableMap<Long, Category> oldCategories;
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
CategorizationChange(ImageGalleryController controller, TagName newCategory, Map<Long, TagName> oldCategories) {
|
CategorizationChange(ImageGalleryController controller, Category newCategory, Map<Long, Category> oldCategories) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.newCategory = newCategory;
|
this.newCategory = newCategory;
|
||||||
this.oldCategories = ImmutableMap.copyOf(oldCategories);
|
this.oldCategories = ImmutableMap.copyOf(oldCategories);
|
||||||
@ -213,8 +203,8 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
CategorizeAction categorizeAction = new CategorizeAction(controller);
|
CategorizeAction categorizeAction = new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false);
|
||||||
categorizeAction.addTagsToFiles(newCategory, "", this.oldCategories.keySet(), false);
|
categorizeAction.addCatToFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,9 +213,10 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void undo() {
|
public void undo() {
|
||||||
CategorizeAction categorizeAction = new CategorizeAction(controller);
|
|
||||||
for (Map.Entry<Long, TagName> entry : oldCategories.entrySet()) {
|
for (Map.Entry<Long, Category> entry : oldCategories.entrySet()) {
|
||||||
categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false);
|
new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false)
|
||||||
|
.addCatToFiles();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,21 +19,19 @@
|
|||||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import java.util.Set;
|
|
||||||
import org.controlsfx.control.action.Action;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class CategorizeGroupAction extends Action {
|
public class CategorizeGroupAction extends CategorizeAction {
|
||||||
|
|
||||||
public CategorizeGroupAction(Category cat, ImageGalleryController controller) {
|
public CategorizeGroupAction(Category cat, ImageGalleryController controller) {
|
||||||
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> {
|
super(controller, cat, null);
|
||||||
Set<Long> fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs());
|
setEventHandler(actionEvent ->
|
||||||
new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet);
|
new CategorizeAction(controller, cat, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs()))
|
||||||
});
|
.handle(actionEvent)
|
||||||
setGraphic(cat.getGraphic());
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||||
|
|
||||||
import org.controlsfx.control.action.Action;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class CategorizeSelectedFilesAction extends Action {
|
public class CategorizeSelectedFilesAction extends CategorizeAction {
|
||||||
|
|
||||||
public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) {
|
public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) {
|
||||||
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), ""));
|
super(controller, cat, null);
|
||||||
setGraphic(cat.getGraphic());
|
setEventHandler(actionEvent ->
|
||||||
|
new CategorizeAction(controller, cat, controller.getSelectionModel().getSelected())
|
||||||
|
.handle(actionEvent)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,21 +19,19 @@
|
|||||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import java.util.Set;
|
|
||||||
import org.controlsfx.control.action.Action;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class TagGroupAction extends Action {
|
public class TagGroupAction extends AddTagAction {
|
||||||
|
|
||||||
public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
|
public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
|
||||||
super(tagName.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> {
|
super(controller, tagName, null);
|
||||||
Set<Long> fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs());
|
setEventHandler(actionEvent ->
|
||||||
new AddDrawableTagAction(controller).addTagsToFiles(tagName, "", fileIdSet);
|
new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())).
|
||||||
});
|
handle(actionEvent)
|
||||||
setGraphic(controller.getTagsManager().getGraphic(tagName));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||||
|
|
||||||
import org.controlsfx.control.action.Action;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class TagSelectedFilesAction extends Action {
|
public class TagSelectedFilesAction extends AddTagAction {
|
||||||
|
|
||||||
public TagSelectedFilesAction(final TagName tagName, ImageGalleryController controller) {
|
public TagSelectedFilesAction(final TagName tagName, ImageGalleryController controller) {
|
||||||
super(tagName.getDisplayName(), actionEvent -> new AddDrawableTagAction(controller).addTag(tagName, ""));
|
super(controller, tagName, null);
|
||||||
setGraphic(controller.getTagsManager().getGraphic(tagName));
|
setEventHandler(actionEvent ->
|
||||||
|
new AddTagAction(controller, tagName, controller.getSelectionModel().getSelected()).
|
||||||
|
handle(actionEvent)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.Background;
|
import javafx.scene.layout.Background;
|
||||||
import javafx.scene.layout.BackgroundFill;
|
import javafx.scene.layout.BackgroundFill;
|
||||||
import javafx.scene.layout.Border;
|
import javafx.scene.layout.Border;
|
||||||
@ -94,6 +97,7 @@ public enum Category {
|
|||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
||||||
private final int id;
|
private final int id;
|
||||||
|
private Image snapshot;
|
||||||
|
|
||||||
private Category(Color color, int id, String name) {
|
private Category(Color color, int id, String name) {
|
||||||
this.color = color;
|
this.color = color;
|
||||||
@ -118,11 +122,15 @@ public enum Category {
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Node getGraphic() {
|
synchronized public Node getGraphic() {
|
||||||
Region region = new Region();
|
if (snapshot == null) {
|
||||||
region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY)));
|
Region region = new Region();
|
||||||
region.setPrefSize(16, 16);
|
region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY)));
|
||||||
region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2)));
|
region.setPrefSize(16, 16);
|
||||||
return region;
|
region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2)));
|
||||||
|
Scene scene = new Scene(region, 16, 16, Color.TRANSPARENT);
|
||||||
|
snapshot = region.snapshot(null, null);
|
||||||
|
}
|
||||||
|
return new ImageView(snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -903,16 +903,11 @@ public final class DrawableDB {
|
|||||||
StringBuilder query = new StringBuilder("SELECT " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files GROUP BY " + groupBy.attrName.toString()); //NON-NLS
|
StringBuilder query = new StringBuilder("SELECT " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files GROUP BY " + groupBy.attrName.toString()); //NON-NLS
|
||||||
|
|
||||||
String orderByClause = "";
|
String orderByClause = "";
|
||||||
switch (sortBy) {
|
|
||||||
case GROUP_BY_VALUE:
|
if (sortBy == GROUP_BY_VALUE) {
|
||||||
orderByClause = " ORDER BY " + groupBy.attrName.toString(); //NON-NLS
|
orderByClause = " ORDER BY " + groupBy.attrName.toString();
|
||||||
break;
|
} else if (sortBy == GroupSortBy.FILE_COUNT) {
|
||||||
case FILE_COUNT:
|
orderByClause = " ORDER BY COUNT(*)";
|
||||||
orderByClause = " ORDER BY COUNT(*)"; //NON-NLS
|
|
||||||
break;
|
|
||||||
case NONE:
|
|
||||||
// case PRIORITY:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query.append(orderByClause);
|
query.append(orderByClause);
|
||||||
|
@ -45,13 +45,24 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
* Manages Tags, Tagging, and the relationship between Categories and Tags in
|
* Manages Tags, Tagging, and the relationship between Categories and Tags in
|
||||||
* the autopsy Db. Delegates some work to the backing {@link TagsManager}.
|
* the autopsy Db. Delegates some work to the backing {@link TagsManager}.
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up"})
|
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up",
|
||||||
|
"DrawableTagsManager.bookMark=Bookmark"})
|
||||||
public class DrawableTagsManager {
|
public class DrawableTagsManager {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName());
|
||||||
|
|
||||||
private static final String FOLLOW_UP = Bundle.DrawableTagsManager_followUp();
|
private static final String FOLLOW_UP = Bundle.DrawableTagsManager_followUp();
|
||||||
|
private static final String BOOKMARK = Bundle.DrawableTagsManager_bookMark();
|
||||||
private static Image FOLLOW_UP_IMAGE;
|
private static Image FOLLOW_UP_IMAGE;
|
||||||
|
private static Image BOOKMARK_IMAGE;
|
||||||
|
|
||||||
|
public static String getFollowUpText() {
|
||||||
|
return FOLLOW_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBookmarkText() {
|
||||||
|
return BOOKMARK;
|
||||||
|
}
|
||||||
|
|
||||||
final private Object autopsyTagsManagerLock = new Object();
|
final private Object autopsyTagsManagerLock = new Object();
|
||||||
private TagsManager autopsyTagsManager;
|
private TagsManager autopsyTagsManager;
|
||||||
@ -70,6 +81,7 @@ public class DrawableTagsManager {
|
|||||||
* The tag name corresponding to the "built-in" tag "Follow Up"
|
* The tag name corresponding to the "built-in" tag "Follow Up"
|
||||||
*/
|
*/
|
||||||
private TagName followUpTagName;
|
private TagName followUpTagName;
|
||||||
|
private TagName bookmarkTagName;
|
||||||
|
|
||||||
public DrawableTagsManager(TagsManager autopsyTagsManager) {
|
public DrawableTagsManager(TagsManager autopsyTagsManager) {
|
||||||
this.autopsyTagsManager = autopsyTagsManager;
|
this.autopsyTagsManager = autopsyTagsManager;
|
||||||
@ -141,6 +153,15 @@ public class DrawableTagsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getBookmarkTagName() throws TskCoreException {
|
||||||
|
synchronized (autopsyTagsManagerLock) {
|
||||||
|
if (Objects.isNull(bookmarkTagName)) {
|
||||||
|
bookmarkTagName = getTagName(BOOKMARK);
|
||||||
|
}
|
||||||
|
return bookmarkTagName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get all the TagNames that are not categories
|
* get all the TagNames that are not categories
|
||||||
*
|
*
|
||||||
@ -192,7 +213,7 @@ public class DrawableTagsManager {
|
|||||||
} catch (TagsManager.TagNameAlreadyExistsException ex) {
|
} catch (TagsManager.TagNameAlreadyExistsException ex) {
|
||||||
throw new TskCoreException("tagame exists but wasn't found", ex);
|
throw new TskCoreException("tagame exists but wasn't found", ex);
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException ex) {
|
} catch (NullPointerException | IllegalStateException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS
|
LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS
|
||||||
throw new TskCoreException("Case was closed out from underneath", ex);
|
throw new TskCoreException("Case was closed out from underneath", ex);
|
||||||
}
|
}
|
||||||
@ -241,9 +262,11 @@ public class DrawableTagsManager {
|
|||||||
try {
|
try {
|
||||||
if (tagname.equals(getFollowUpTagName())) {
|
if (tagname.equals(getFollowUpTagName())) {
|
||||||
return new ImageView(getFollowUpImage());
|
return new ImageView(getFollowUpImage());
|
||||||
|
} else if (tagname.equals(getBookmarkTagName())) {
|
||||||
|
return new ImageView(getBookmarkImage());
|
||||||
}
|
}
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" tag name from db.", ex);
|
LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex);
|
||||||
}
|
}
|
||||||
return DrawableAttribute.TAGS.getGraphicForValue(tagname);
|
return DrawableAttribute.TAGS.getGraphicForValue(tagname);
|
||||||
}
|
}
|
||||||
@ -254,4 +277,12 @@ public class DrawableTagsManager {
|
|||||||
}
|
}
|
||||||
return FOLLOW_UP_IMAGE;
|
return FOLLOW_UP_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized private static Image getBookmarkImage() {
|
||||||
|
if (BOOKMARK_IMAGE == null) {
|
||||||
|
BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
|
||||||
|
}
|
||||||
|
return BOOKMARK_IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -126,7 +127,7 @@ public class GroupManager {
|
|||||||
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
|
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
|
||||||
private volatile SortOrder sortOrder = SortOrder.ASCENDING;
|
private volatile SortOrder sortOrder = SortOrder.ASCENDING;
|
||||||
|
|
||||||
private final ReadOnlyObjectWrapper<GroupSortBy> sortByProp = new ReadOnlyObjectWrapper<>(sortBy);
|
private final ReadOnlyObjectWrapper< Comparator<DrawableGroup>> sortByProp = new ReadOnlyObjectWrapper<>(sortBy);
|
||||||
private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(groupBy);
|
private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(groupBy);
|
||||||
private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder);
|
private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder);
|
||||||
|
|
||||||
@ -274,7 +275,7 @@ public class GroupManager {
|
|||||||
} else if (unSeenGroups.contains(group) == false) {
|
} else if (unSeenGroups.contains(group) == false) {
|
||||||
unSeenGroups.add(group);
|
unSeenGroups.add(group);
|
||||||
}
|
}
|
||||||
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
|
FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -299,11 +300,11 @@ public class GroupManager {
|
|||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (analyzedGroups.contains(group)) {
|
if (analyzedGroups.contains(group)) {
|
||||||
analyzedGroups.remove(group);
|
analyzedGroups.remove(group);
|
||||||
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
|
FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
|
||||||
}
|
}
|
||||||
if (unSeenGroups.contains(group)) {
|
if (unSeenGroups.contains(group)) {
|
||||||
unSeenGroups.remove(group);
|
unSeenGroups.remove(group);
|
||||||
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
|
FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -450,7 +451,7 @@ public class GroupManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupSortBy getSortBy() {
|
public Comparator<DrawableGroup> getSortBy() {
|
||||||
return sortBy;
|
return sortBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,7 +460,7 @@ public class GroupManager {
|
|||||||
Platform.runLater(() -> sortByProp.set(sortBy));
|
Platform.runLater(() -> sortByProp.set(sortBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<GroupSortBy> getSortByProperty() {
|
public ReadOnlyObjectProperty< Comparator<DrawableGroup>> getSortByProperty() {
|
||||||
return sortByProp.getReadOnlyProperty();
|
return sortByProp.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,8 +524,8 @@ public class GroupManager {
|
|||||||
setSortBy(sortBy);
|
setSortBy(sortBy);
|
||||||
setSortOrder(sortOrder);
|
setSortOrder(sortOrder);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
|
FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
|
||||||
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
|
FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -666,7 +667,7 @@ public class GroupManager {
|
|||||||
group = new DrawableGroup(groupKey, fileIDs, groupSeen);
|
group = new DrawableGroup(groupKey, fileIDs, groupSeen);
|
||||||
controller.getCategoryManager().registerListener(group);
|
controller.getCategoryManager().registerListener(group);
|
||||||
group.seenProperty().addListener((o, oldSeen, newSeen) -> {
|
group.seenProperty().addListener((o, oldSeen, newSeen) -> {
|
||||||
markGroupSeen(group, newSeen);
|
Platform.runLater(() -> markGroupSeen(group, newSeen));
|
||||||
});
|
});
|
||||||
groupMap.put(groupKey, group);
|
groupMap.put(groupKey, group);
|
||||||
}
|
}
|
||||||
@ -675,7 +676,7 @@ public class GroupManager {
|
|||||||
if (analyzedGroups.contains(group) == false) {
|
if (analyzedGroups.contains(group) == false) {
|
||||||
analyzedGroups.add(group);
|
analyzedGroups.add(group);
|
||||||
if (Objects.isNull(task)) {
|
if (Objects.isNull(task)) {
|
||||||
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
|
FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
markGroupSeen(group, groupSeen);
|
markGroupSeen(group, groupSeen);
|
||||||
@ -719,12 +720,12 @@ public class GroupManager {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
@NbBundle.Messages({"# {0} - groupBy attribute Name",
|
@NbBundle.Messages({"# {0} - groupBy attribute Name",
|
||||||
"# {1} - sortBy name",
|
"# {1} - sortBy name",
|
||||||
"# {2} - sort Order",
|
"# {2} - sort Order",
|
||||||
"ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order",
|
"ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order",
|
||||||
"# {0} - groupBy attribute Name",
|
"# {0} - groupBy attribute Name",
|
||||||
"# {1} - atribute value",
|
"# {1} - atribute value",
|
||||||
"ReGroupTask.progressUpdate=regrouping files by {0} : {1}"})
|
"ReGroupTask.progressUpdate=regrouping files by {0} : {1}"})
|
||||||
private class ReGroupTask<A extends Comparable<A>> extends LoggedTask<Void> {
|
private class ReGroupTask<A extends Comparable<A>> extends LoggedTask<Void> {
|
||||||
|
|
||||||
private ProgressHandle groupProgress;
|
private ProgressHandle groupProgress;
|
||||||
@ -735,8 +736,8 @@ public class GroupManager {
|
|||||||
|
|
||||||
private final SortOrder sortOrder;
|
private final SortOrder sortOrder;
|
||||||
|
|
||||||
public ReGroupTask(DrawableAttribute<A> groupBy, GroupSortBy sortBy, SortOrder sortOrder) {
|
ReGroupTask(DrawableAttribute<A> groupBy, GroupSortBy sortBy, SortOrder sortOrder) {
|
||||||
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.name(), sortOrder.toString()), true);
|
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true);
|
||||||
|
|
||||||
this.groupBy = groupBy;
|
this.groupBy = groupBy;
|
||||||
this.sortBy = sortBy;
|
this.sortBy = sortBy;
|
||||||
@ -755,7 +756,7 @@ public class GroupManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
groupProgress = ProgressHandleFactory.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.name(), sortOrder.toString()), this);
|
groupProgress = ProgressHandleFactory.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
analyzedGroups.clear();
|
analyzedGroups.clear();
|
||||||
unSeenGroups.clear();
|
unSeenGroups.clear();
|
||||||
@ -778,7 +779,7 @@ public class GroupManager {
|
|||||||
groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p);
|
groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p);
|
||||||
popuplateIfAnalyzed(new GroupKey<A>(groupBy, val), this);
|
popuplateIfAnalyzed(new GroupKey<A>(groupBy, val), this);
|
||||||
}
|
}
|
||||||
Platform.runLater(() -> FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder)));
|
Platform.runLater(() -> FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)));
|
||||||
|
|
||||||
updateProgress(1, 1);
|
updateProgress(1, 1);
|
||||||
return null;
|
return null;
|
||||||
@ -793,4 +794,16 @@ public class GroupManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T> Comparator<T> applySortOrder(final SortOrder sortOrder, Comparator<T> comparator) {
|
||||||
|
switch (sortOrder) {
|
||||||
|
case ASCENDING:
|
||||||
|
return comparator;
|
||||||
|
case DESCENDING:
|
||||||
|
return comparator.reversed();
|
||||||
|
case UNSORTED:
|
||||||
|
default:
|
||||||
|
return new GroupSortBy.AllEqualComparator<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,89 +18,50 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
|
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javax.swing.SortOrder;
|
|
||||||
import static javax.swing.SortOrder.ASCENDING;
|
|
||||||
import static javax.swing.SortOrder.DESCENDING;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* enum of possible properties to sort groups by. This is the model for the drop
|
* Pseudo enum of possible properties to sort groups by.
|
||||||
* down in Toolbar as well as each enum value having the stategy
|
|
||||||
* ({@link Comparator}) for sorting the groups
|
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"GroupSortBy.groupSize=Group Size",
|
@NbBundle.Messages({"GroupSortBy.groupSize=Group Size",
|
||||||
"GroupSortBy.groupName=Group Name",
|
"GroupSortBy.groupName=Group Name",
|
||||||
"GroupSortBy.none=None",
|
"GroupSortBy.none=None",
|
||||||
"GroupSortBy.priority=Priority"})
|
"GroupSortBy.priority=Priority"})
|
||||||
public enum GroupSortBy implements ComparatorProvider {
|
public class GroupSortBy implements Comparator<DrawableGroup> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sort the groups by the number of files in each sort the groups by the
|
* sort the groups by the number of files in each
|
||||||
* number of files in each
|
|
||||||
*/
|
*/
|
||||||
FILE_COUNT(Bundle.GroupSortBy_groupSize(), true, "folder-open-image.png") { //NON-NLS
|
public final static GroupSortBy FILE_COUNT = new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png", Comparator.comparing(DrawableGroup::getSize));
|
||||||
@Override
|
|
||||||
public Comparator<DrawableGroup> getGrpComparator(final SortOrder sortOrder) {
|
|
||||||
return applySortOrder(sortOrder, Comparator.comparingInt(DrawableGroup::getSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <A extends Comparable<A>> Comparator<A> getValueComparator(final DrawableAttribute<A> attr, final SortOrder sortOrder) {
|
|
||||||
return getDefaultValueComparator(attr, sortOrder);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* sort the groups by the natural order of the grouping value ( eg group
|
* sort the groups by the natural order of the grouping value ( eg group
|
||||||
* them by path alphabetically )
|
* them by path alphabetically )
|
||||||
*/
|
*/
|
||||||
GROUP_BY_VALUE(Bundle.GroupSortBy_groupName(), true, "folder-rename.png") { //NON-NLS
|
public final static GroupSortBy GROUP_BY_VALUE = new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png", Comparator.comparing(DrawableGroup::getGroupByValueDislpayName));
|
||||||
@Override
|
|
||||||
public Comparator<DrawableGroup> getGrpComparator(final SortOrder sortOrder) {
|
|
||||||
return applySortOrder(sortOrder, Comparator.comparing(t -> t.getGroupByValueDislpayName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <A extends Comparable<A>> Comparator<A> getValueComparator(final DrawableAttribute<A> attr, final SortOrder sortOrder) {
|
|
||||||
return applySortOrder(sortOrder, Comparator.<A>naturalOrder());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* don't sort the groups just use what ever order they come in (ingest
|
* don't sort the groups just use what ever order they come in (ingest
|
||||||
* order)
|
* order)
|
||||||
*/
|
*/
|
||||||
NONE(Bundle.GroupSortBy_none(), false, "prohibition.png") { //NON-NLS
|
public final static GroupSortBy NONE = new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png", new AllEqualComparator<>());
|
||||||
@Override
|
|
||||||
public Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder) {
|
|
||||||
return new NoOpComparator<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <A extends Comparable<A>> Comparator<A> getValueComparator(DrawableAttribute<A> attr, final SortOrder sortOrder) {
|
|
||||||
return new NoOpComparator<>();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* sort the groups by some priority metric to be determined and implemented
|
* sort the groups by some priority metric to be determined and implemented
|
||||||
*/
|
*/
|
||||||
PRIORITY(Bundle.GroupSortBy_priority(), false, "hashset_hits.png") { //NON-NLS
|
public final static GroupSortBy PRIORITY = new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png", Comparator.comparing(DrawableGroup::getHashHitDensity).thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount)));
|
||||||
@Override
|
|
||||||
public Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder) {
|
|
||||||
return Comparator.nullsLast(Comparator.comparingDouble(DrawableGroup::getHashHitDensity).thenComparingInt(DrawableGroup::getSize).reversed());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <A extends Comparable<A>> Comparator<A> getValueComparator(DrawableAttribute<A> attr, SortOrder sortOrder) {
|
public int compare(DrawableGroup o1, DrawableGroup o2) {
|
||||||
return getDefaultValueComparator(attr, sortOrder);
|
return delegate.compare(o1, o2);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
private final static ObservableList<GroupSortBy> values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get a list of the values of this enum
|
* get a list of the values of this enum
|
||||||
@ -108,8 +69,7 @@ public enum GroupSortBy implements ComparatorProvider {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static ObservableList<GroupSortBy> getValues() {
|
public static ObservableList<GroupSortBy> getValues() {
|
||||||
return FXCollections.observableArrayList(Arrays.asList(values()));
|
return values;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final private String displayName;
|
final private String displayName;
|
||||||
@ -118,12 +78,12 @@ public enum GroupSortBy implements ComparatorProvider {
|
|||||||
|
|
||||||
private final String imageName;
|
private final String imageName;
|
||||||
|
|
||||||
private final Boolean sortOrderEnabled;
|
private final Comparator<DrawableGroup> delegate;
|
||||||
|
|
||||||
private GroupSortBy(String displayName, Boolean sortOrderEnabled, String imagePath) {
|
private GroupSortBy(String displayName, String imagePath, Comparator<DrawableGroup> internalComparator) {
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.sortOrderEnabled = sortOrderEnabled;
|
|
||||||
this.imageName = imagePath;
|
this.imageName = imagePath;
|
||||||
|
this.delegate = internalComparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
@ -139,49 +99,11 @@ public enum GroupSortBy implements ComparatorProvider {
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isSortOrderEnabled() {
|
static class AllEqualComparator<A> implements Comparator<A> {
|
||||||
return sortOrderEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Comparator<T> applySortOrder(final SortOrder sortOrder, Comparator<T> comparator) {
|
|
||||||
switch (sortOrder) {
|
|
||||||
case ASCENDING:
|
|
||||||
return comparator;
|
|
||||||
case DESCENDING:
|
|
||||||
return comparator.reversed();
|
|
||||||
case UNSORTED:
|
|
||||||
default:
|
|
||||||
return new NoOpComparator<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NoOpComparator<A> implements Comparator<A> {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(A o1, A o2) {
|
public int compare(A o1, A o2) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* * implementers of this interface must provide a method to compare
|
|
||||||
* ({@link Comparable}) values and Groupings based on an
|
|
||||||
* {@link DrawableAttribute} and a {@link SortOrder}
|
|
||||||
*/
|
|
||||||
interface ComparatorProvider {
|
|
||||||
|
|
||||||
<A extends Comparable<A>> Comparator<A> getValueComparator(DrawableAttribute<A> attr, SortOrder sortOrder);
|
|
||||||
|
|
||||||
Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder);
|
|
||||||
|
|
||||||
default <A extends Comparable<A>> Comparator<A> getDefaultValueComparator(DrawableAttribute<A> attr, SortOrder sortOrder) {
|
|
||||||
return (A v1, A v2) -> {
|
|
||||||
DrawableGroup g1 = ImageGalleryController.getDefault().getGroupManager().getGroupForKey(new GroupKey<>(attr, v1));
|
|
||||||
DrawableGroup g2 = ImageGalleryController.getDefault().getGroupManager().getGroupForKey(new GroupKey<>(attr, v2));
|
|
||||||
|
|
||||||
return getGrpComparator(sortOrder).compare(g1, g2);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.ComboBox?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.RadioButton?>
|
||||||
|
<?import javafx.scene.control.ToggleGroup?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import org.controlsfx.control.SegmentedButton?>
|
||||||
|
|
||||||
|
<fx:root id="HBox" alignment="CENTER" spacing="5.0" type="HBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="label" text="Sort By:">
|
||||||
|
<labelFor>
|
||||||
|
<ComboBox fx:id="sortByBox" />
|
||||||
|
</labelFor>
|
||||||
|
</Label>
|
||||||
|
<fx:reference fx:id="sortBox" source="sortByBox" />
|
||||||
|
<SegmentedButton>
|
||||||
|
<buttons>
|
||||||
|
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true">
|
||||||
|
<toggleGroup>
|
||||||
|
<ToggleGroup fx:id="orderGroup" />
|
||||||
|
</toggleGroup>
|
||||||
|
<graphic>
|
||||||
|
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@../images/sort_ascending.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="Ascending" />
|
||||||
|
</tooltip>
|
||||||
|
</RadioButton>
|
||||||
|
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" toggleGroup="$orderGroup">
|
||||||
|
<graphic>
|
||||||
|
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@../images/sort_descending.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="Descending" />
|
||||||
|
</tooltip>
|
||||||
|
</RadioButton>
|
||||||
|
</buttons>
|
||||||
|
</SegmentedButton>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</fx:root>
|
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.control.RadioButton;
|
||||||
|
import javafx.scene.control.ToggleGroup;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javax.swing.SortOrder;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SortChooser<X, Y extends Comparator<X>> extends HBox {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private RadioButton ascRadio;
|
||||||
|
@FXML
|
||||||
|
private RadioButton descRadio;
|
||||||
|
@FXML
|
||||||
|
private ToggleGroup orderGroup;
|
||||||
|
@FXML
|
||||||
|
private ComboBox<Y> sortByBox;
|
||||||
|
|
||||||
|
private final ObservableList<Y> comparators;
|
||||||
|
|
||||||
|
private final ReadOnlyObjectWrapper<SortOrder> sortOrder = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING);
|
||||||
|
private final SimpleBooleanProperty sortOrderDisabled = new SimpleBooleanProperty(false);
|
||||||
|
private final SimpleObjectProperty<ValueType> valueType = new SimpleObjectProperty<>(ValueType.NUMERIC);
|
||||||
|
|
||||||
|
public SortChooser(ObservableList<Y> comps) {
|
||||||
|
this.comparators = comps;
|
||||||
|
FXMLConstructor.construct(this, "SortChooser.fxml");
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void initialize() {
|
||||||
|
assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
|
assert descRadio != null : "fx:id=\"descRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
|
assert orderGroup != null : "fx:id=\"orderGroup\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
|
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
|
|
||||||
|
ascRadio.getStyleClass().remove("radio-button");
|
||||||
|
ascRadio.getStyleClass().add("toggle-button");
|
||||||
|
descRadio.getStyleClass().remove("radio-button");
|
||||||
|
descRadio.getStyleClass().add("toggle-button");
|
||||||
|
|
||||||
|
valueType.addListener((observable, oldValue, newValue) -> {
|
||||||
|
ascRadio.setGraphic(new ImageView(newValue.getAscendingImage()));
|
||||||
|
descRadio.setGraphic(new ImageView(newValue.getDescendingImage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
ascRadio.disableProperty().bind(sortOrderDisabled);
|
||||||
|
descRadio.disableProperty().bind(sortOrderDisabled);
|
||||||
|
ascRadio.selectedProperty().addListener(selectedToggle -> {
|
||||||
|
sortOrder.set(orderGroup.getSelectedToggle() == ascRadio ? SortOrder.ASCENDING : SortOrder.DESCENDING);
|
||||||
|
});
|
||||||
|
|
||||||
|
sortByBox.setItems(comparators);
|
||||||
|
sortByBox.setCellFactory(listView -> new ComparatorCell());
|
||||||
|
sortByBox.setButtonCell(new ComparatorCell());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueType getValueType() {
|
||||||
|
return valueType.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueType(ValueType type) {
|
||||||
|
valueType.set(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleObjectProperty<ValueType> valueTypeProperty() {
|
||||||
|
return valueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSortOrderDisabled(boolean disabled) {
|
||||||
|
sortOrderDisabled.set(disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSortOrderDisabled() {
|
||||||
|
return sortOrderDisabled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleBooleanProperty sortOrderDisabledProperty() {
|
||||||
|
return sortOrderDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SortOrder getSortOrder() {
|
||||||
|
return sortOrder.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<SortOrder> sortOrderProperty() {
|
||||||
|
return sortOrder.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Y getComparator() {
|
||||||
|
return sortByBox.getSelectionModel().getSelectedItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComparator(Y selected) {
|
||||||
|
sortByBox.getSelectionModel().select(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<Y> comparatorProperty() {
|
||||||
|
return sortByBox.getSelectionModel().selectedItemProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ValueType {
|
||||||
|
|
||||||
|
LEXICOGRAPHIC("sort_asc_az.png", "sort_desc_az.png"),
|
||||||
|
NUMERIC("sort_ascending.png", "sort_descending.png");
|
||||||
|
|
||||||
|
private final Image ascImage;
|
||||||
|
private final Image descImage;
|
||||||
|
|
||||||
|
private ValueType(String ascImageName, String descImageName) {
|
||||||
|
this.ascImage = new Image("/org/sleuthkit/autopsy/imagegallery/images/" + ascImageName);
|
||||||
|
this.descImage = new Image("/org/sleuthkit/autopsy/imagegallery/images/" + descImageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image getAscendingImage() {
|
||||||
|
return ascImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image getDescendingImage() {
|
||||||
|
return descImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ComparatorCell extends ListCell<Y> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Y item, boolean empty) {
|
||||||
|
super.updateItem(item, empty); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
|
||||||
|
if (empty || null == item) {
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
String displayName = (String) item.getClass().getMethod("getDisplayName").invoke(item);
|
||||||
|
setText(displayName);
|
||||||
|
Image icon = (Image) item.getClass().getMethod("getIcon").invoke(item);
|
||||||
|
setGraphic(new ImageView(icon));
|
||||||
|
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||||
|
// Exceptions.printStackTrace(ex);
|
||||||
|
setText(item.toString());
|
||||||
|
setGraphic(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import java.lang.*?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.geometry.*?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.ProgressBar?>
|
||||||
<?import javafx.scene.image.*?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
|
||||||
<fx:root id="AnchorPane" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
<fx:root id="AnchorPane" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.AnchorPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<children>
|
<children>
|
||||||
<BorderPane minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
<BorderPane minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
<right>
|
<right>
|
||||||
<HBox alignment="CENTER_RIGHT" prefHeight="-1.0" prefWidth="-1.0" BorderPane.alignment="CENTER_RIGHT">
|
<HBox alignment="CENTER_RIGHT" prefHeight="-1.0" prefWidth="-1.0" spacing="5.0" BorderPane.alignment="CENTER_RIGHT">
|
||||||
<children>
|
<children>
|
||||||
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
|
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
|
||||||
<children>
|
<children>
|
||||||
@ -18,7 +22,10 @@
|
|||||||
<Label id="fileUpdateLabel" fx:id="fileUpdateTaskLabel" alignment="CENTER" contentDisplay="CENTER" graphicTextGap="0.0" labelFor="$fileTaskProgresBar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="-1.0" text="0 File Update Tasks" StackPane.alignment="CENTER">
|
<Label id="fileUpdateLabel" fx:id="fileUpdateTaskLabel" alignment="CENTER" contentDisplay="CENTER" graphicTextGap="0.0" labelFor="$fileTaskProgresBar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="-1.0" text="0 File Update Tasks" StackPane.alignment="CENTER">
|
||||||
<StackPane.margin>
|
<StackPane.margin>
|
||||||
<Insets left="3.0" right="3.0" />
|
<Insets left="3.0" right="3.0" />
|
||||||
</StackPane.margin></Label>
|
</StackPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||||
|
</padding></Label>
|
||||||
</children>
|
</children>
|
||||||
<HBox.margin>
|
<HBox.margin>
|
||||||
<Insets />
|
<Insets />
|
||||||
@ -27,10 +34,13 @@
|
|||||||
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
|
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
|
||||||
<children>
|
<children>
|
||||||
<ProgressBar fx:id="bgTaskProgressBar" maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-1.0" prefHeight="24.0" prefWidth="-1.0" progress="0.0" StackPane.alignment="CENTER" />
|
<ProgressBar fx:id="bgTaskProgressBar" maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-1.0" prefHeight="24.0" prefWidth="-1.0" progress="0.0" StackPane.alignment="CENTER" />
|
||||||
<Label fx:id="bgTaskLabel" alignment="CENTER" cache="false" contentDisplay="CENTER" disable="false" focusTraversable="false" labelFor="$uiTaskProgressBar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" text="" StackPane.alignment="CENTER">
|
<Label fx:id="bgTaskLabel" alignment="CENTER" cache="false" contentDisplay="CENTER" disable="false" focusTraversable="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" text="" StackPane.alignment="CENTER">
|
||||||
<StackPane.margin>
|
<StackPane.margin>
|
||||||
<Insets left="3.0" right="3.0" />
|
<Insets left="3.0" right="3.0" />
|
||||||
</StackPane.margin></Label>
|
</StackPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||||
|
</padding></Label>
|
||||||
</children>
|
</children>
|
||||||
<HBox.margin>
|
<HBox.margin>
|
||||||
<Insets right="5.0" />
|
<Insets right="5.0" />
|
||||||
@ -42,19 +52,6 @@
|
|||||||
</BorderPane.margin>
|
</BorderPane.margin>
|
||||||
</HBox>
|
</HBox>
|
||||||
</right>
|
</right>
|
||||||
<center>
|
|
||||||
<HBox>
|
|
||||||
<children>
|
|
||||||
<Label fx:id="statusLabel" maxWidth="-Infinity" minWidth="-Infinity" wrapText="true" BorderPane.alignment="CENTER" HBox.hgrow="ALWAYS">
|
|
||||||
<BorderPane.margin>
|
|
||||||
<Insets left="10.0" right="10.0" />
|
|
||||||
</BorderPane.margin>
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="10.0" right="10.0" />
|
|
||||||
</HBox.margin></Label>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
</center>
|
|
||||||
<left><Label fx:id="staleLabel" text="Some data may be out of date. Enable listening to ingest to update." BorderPane.alignment="CENTER">
|
<left><Label fx:id="staleLabel" text="Some data may be out of date. Enable listening to ingest to update." BorderPane.alignment="CENTER">
|
||||||
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-14 Basis Technology Corp.
|
* Copyright 2013-16 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,8 +19,6 @@
|
|||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
@ -38,21 +36,12 @@ public class StatusBar extends AnchorPane {
|
|||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ProgressBar fileTaskProgresBar;
|
private ProgressBar fileTaskProgresBar;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label fileUpdateTaskLabel;
|
private Label fileUpdateTaskLabel;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label statusLabel;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label bgTaskLabel;
|
private Label bgTaskLabel;
|
||||||
|
|
||||||
@ -64,42 +53,32 @@ public class StatusBar extends AnchorPane {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@NbBundle.Messages({"StatusBar.fileUpdateTaskLabel.text= File Update Tasks",
|
@NbBundle.Messages({"StatusBar.fileUpdateTaskLabel.text= File Update Tasks",
|
||||||
"StatusBar.bgTaskLabel.text=Regrouping",
|
"StatusBar.bgTaskLabel.text=Regrouping",
|
||||||
"StatuBar.toolTip=Some data may be out of date. Enable Image Gallery in Tools | Options | Image /Video Gallery , after ingest is complete to update the Image Gallery data."})
|
"StatuBar.toolTip=Some data may be out of date. Enable Image Gallery in Tools | Options | Image /Video Gallery , after ingest is complete to update the Image Gallery data."})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert fileTaskProgresBar != null : "fx:id=\"fileTaskProgresBar\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
assert fileTaskProgresBar != null : "fx:id=\"fileTaskProgresBar\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
||||||
assert fileUpdateTaskLabel != null : "fx:id=\"fileUpdateTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
assert fileUpdateTaskLabel != null : "fx:id=\"fileUpdateTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
||||||
assert statusLabel != null : "fx:id=\"statusLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
|
||||||
assert bgTaskLabel != null : "fx:id=\"bgTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
assert bgTaskLabel != null : "fx:id=\"bgTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
||||||
assert bgTaskProgressBar != null : "fx:id=\"bgTaskProgressBar\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
assert bgTaskProgressBar != null : "fx:id=\"bgTaskProgressBar\" was not injected: check your FXML file 'StatusBar.fxml'.";
|
||||||
|
|
||||||
fileUpdateTaskLabel.textProperty().bind(controller.getFileUpdateQueueSizeProperty().asString().concat(Bundle.StatusBar_fileUpdateTaskLabel_text()));//;setText(newSize.toString() + " File Update Tasks");
|
fileUpdateTaskLabel.textProperty().bind(controller.getDBTasksQueueSizeProperty().asString().concat(Bundle.StatusBar_fileUpdateTaskLabel_text()));
|
||||||
fileTaskProgresBar.progressProperty().bind(controller.getFileUpdateQueueSizeProperty().negate());
|
fileTaskProgresBar.progressProperty().bind(controller.getDBTasksQueueSizeProperty().negate());
|
||||||
// controller.getFileUpdateQueueSizeProperty().addListener((ov, oldSize, newSize) -> {
|
|
||||||
// Platform.runLater(() -> {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
controller.regroupProgress().addListener((ov, oldSize, newSize) -> {
|
controller.regroupProgress().addListener((ov, oldSize, newSize) -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if(controller.regroupProgress().lessThan(1.0).get()){
|
if (controller.regroupProgress().lessThan(1.0).get()) {
|
||||||
// Regrouping in progress
|
// Regrouping in progress
|
||||||
bgTaskProgressBar.progressProperty().setValue(-1.0);
|
bgTaskProgressBar.progressProperty().setValue(-1.0);
|
||||||
bgTaskLabel.setText(Bundle.StatusBar_bgTaskLabel_text());
|
bgTaskLabel.setText(Bundle.StatusBar_bgTaskLabel_text());
|
||||||
} else{
|
} else {
|
||||||
// Clear the progress bar
|
// Clear the progress bar
|
||||||
bgTaskProgressBar.progressProperty().setValue(0.0);
|
bgTaskProgressBar.progressProperty().setValue(0.0);
|
||||||
bgTaskLabel.setText("");
|
bgTaskLabel.setText("");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip())));
|
||||||
staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip()));
|
|
||||||
});
|
|
||||||
staleLabel.visibleProperty().bind(controller.stale());
|
staleLabel.visibleProperty().bind(controller.stale());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,14 +95,4 @@ public class StatusBar extends AnchorPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLabelText(final String newText) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
statusLabel.setText(newText);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLabeltext() {
|
|
||||||
return statusLabel.getText();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,113 +1,86 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.ComboBox?>
|
<?import javafx.scene.control.ComboBox?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.MenuItem?>
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<?import javafx.scene.control.RadioButton?>
|
|
||||||
<?import javafx.scene.control.Separator?>
|
<?import javafx.scene.control.Separator?>
|
||||||
<?import javafx.scene.control.Slider?>
|
<?import javafx.scene.control.Slider?>
|
||||||
<?import javafx.scene.control.SplitMenuButton?>
|
<?import javafx.scene.control.SplitMenuButton?>
|
||||||
<?import javafx.scene.control.ToggleGroup?>
|
|
||||||
<?import javafx.scene.control.ToolBar?>
|
<?import javafx.scene.control.ToolBar?>
|
||||||
<?import javafx.scene.image.Image?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.Region?>
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
|
||||||
|
|
||||||
<fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
<fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<items>
|
<items>
|
||||||
<Label fx:id="groupByLabel" text="Group By:">
|
<HBox alignment="CENTER" spacing="5.0">
|
||||||
<labelFor>
|
<children>
|
||||||
<ComboBox fx:id="groupByBox" editable="false" />
|
<Label fx:id="groupByLabel" text="Group By:">
|
||||||
</labelFor>
|
<labelFor>
|
||||||
</Label>
|
<ComboBox fx:id="groupByBox" editable="false" />
|
||||||
<fx:reference source="groupByBox" />
|
</labelFor>
|
||||||
<Region prefHeight="-1.0" prefWidth="10.0" />
|
</Label>
|
||||||
<Label fx:id="sortByLabel" text="Sort By:">
|
<fx:reference source="groupByBox" />
|
||||||
<labelFor>
|
|
||||||
<ComboBox fx:id="sortByBox" />
|
</children>
|
||||||
</labelFor>
|
</HBox>
|
||||||
</Label>
|
|
||||||
<HBox id="HBox" fx:id="sortControlGroup" alignment="CENTER" spacing="5.0">
|
|
||||||
<children>
|
|
||||||
<fx:reference source="sortByBox" />
|
|
||||||
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0">
|
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.0" />
|
||||||
<children>
|
<HBox alignment="CENTER" spacing="5.0">
|
||||||
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending">
|
<children>
|
||||||
<graphic>
|
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
|
||||||
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
<graphic>
|
||||||
<image>
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
<Image url="@../images/arrow_up.png" />
|
<image>
|
||||||
</image>
|
<Image url="@../images/tag_red.png" />
|
||||||
</ImageView>
|
</image>
|
||||||
</graphic>
|
</ImageView>
|
||||||
<toggleGroup>
|
</graphic>
|
||||||
<ToggleGroup fx:id="orderGroup" />
|
</Label>
|
||||||
</toggleGroup>
|
<SplitMenuButton id="tagSplitMenu" fx:id="tagGroupMenuButton" disable="true" mnemonicParsing="false" text="Follow Up" textOverrun="ELLIPSIS">
|
||||||
</RadioButton>
|
<items>
|
||||||
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup">
|
<MenuItem mnemonicParsing="false" text="Action 1" />
|
||||||
<graphic>
|
<MenuItem mnemonicParsing="false" text="Action 2" />
|
||||||
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
</items>
|
||||||
<image>
|
</SplitMenuButton>
|
||||||
<Image url="@../images/arrow_down.png" />
|
</children>
|
||||||
</image>
|
</HBox>
|
||||||
</ImageView>
|
<HBox alignment="CENTER" spacing="5.0">
|
||||||
</graphic>
|
<children>
|
||||||
</RadioButton>
|
<Label fx:id="categoryImageViewLabel" text="Categorize Group's Files:">
|
||||||
</children>
|
<graphic>
|
||||||
</VBox>
|
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||||
</children>
|
<image>
|
||||||
</HBox>
|
<Image url="@../images/category-icon.png" />
|
||||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="20.0" />
|
</image>
|
||||||
<HBox alignment="CENTER" spacing="5.0">
|
</ImageView>
|
||||||
<children>
|
</graphic>
|
||||||
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
|
</Label>
|
||||||
<graphic>
|
<SplitMenuButton id="catSplitMenu" fx:id="catGroupMenuButton" disable="true" mnemonicParsing="false" text="Cat-0">
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<items>
|
||||||
<image>
|
<MenuItem mnemonicParsing="false" text="Action 1" />
|
||||||
<Image url="@../images/tag_red.png" />
|
<MenuItem mnemonicParsing="false" text="Action 2" />
|
||||||
</image>
|
</items>
|
||||||
</ImageView>
|
</SplitMenuButton>
|
||||||
</graphic>
|
</children>
|
||||||
</Label>
|
<padding>
|
||||||
<SplitMenuButton id="tagSplitMenu" fx:id="tagGroupMenuButton" disable="true" mnemonicParsing="false" text="Follow Up" textOverrun="ELLIPSIS">
|
<Insets left="5.0" />
|
||||||
<items>
|
</padding>
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1" />
|
</HBox>
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2" />
|
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.0" />
|
||||||
</items>
|
<HBox alignment="CENTER" spacing="5.0">
|
||||||
</SplitMenuButton>
|
<children>
|
||||||
</children>
|
<Label fx:id="thumbnailSizeLabel" text="Thumbnail Size (px):">
|
||||||
</HBox>
|
<labelFor>
|
||||||
<HBox alignment="CENTER" spacing="5.0">
|
<Slider fx:id="sizeSlider" blockIncrement="100.0" majorTickUnit="100.0" max="300.0" min="100.0" minorTickCount="0" orientation="HORIZONTAL" prefHeight="-1.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="100.0" />
|
||||||
<children>
|
</labelFor>
|
||||||
<Label fx:id="categoryImageViewLabel" text="Categorize Group's Files:">
|
</Label>
|
||||||
<graphic>
|
<fx:reference source="sizeSlider" />
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
</children>
|
||||||
<image>
|
</HBox>
|
||||||
<Image url="@../images/category-icon.png" />
|
</items>
|
||||||
</image>
|
|
||||||
</ImageView>
|
|
||||||
</graphic>
|
|
||||||
</Label>
|
|
||||||
<SplitMenuButton id="catSplitMenu" fx:id="catGroupMenuButton" disable="true" mnemonicParsing="false" text="Cat-0">
|
|
||||||
<items>
|
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1" />
|
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2" />
|
|
||||||
</items>
|
|
||||||
</SplitMenuButton>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="20.0" />
|
|
||||||
<HBox alignment="CENTER" spacing="5.0">
|
|
||||||
<children>
|
|
||||||
<Label fx:id="thumbnailSizeLabel" text="Thumbnail Size (px):">
|
|
||||||
<labelFor>
|
|
||||||
<Slider fx:id="sizeSlider" blockIncrement="100.0" majorTickUnit="100.0" max="300.0" min="100.0" minorTickCount="0" orientation="HORIZONTAL" prefHeight="-1.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="100.0" />
|
|
||||||
</labelFor>
|
|
||||||
</Label>
|
|
||||||
<fx:reference source="sizeSlider" />
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
</items>
|
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
@ -32,14 +32,10 @@ import javafx.fxml.FXML;
|
|||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.control.RadioButton;
|
|
||||||
import javafx.scene.control.Slider;
|
import javafx.scene.control.Slider;
|
||||||
import javafx.scene.control.SplitMenuButton;
|
import javafx.scene.control.SplitMenuButton;
|
||||||
import javafx.scene.control.ToggleGroup;
|
|
||||||
import javafx.scene.control.ToolBar;
|
import javafx.scene.control.ToolBar;
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import javax.swing.SortOrder;
|
import javax.swing.SortOrder;
|
||||||
|
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
@ -48,6 +44,7 @@ import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
|
|||||||
import org.sleuthkit.autopsy.imagegallery.actions.TagGroupAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.TagGroupAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
@ -67,21 +64,6 @@ public class Toolbar extends ToolBar {
|
|||||||
@FXML
|
@FXML
|
||||||
private Slider sizeSlider;
|
private Slider sizeSlider;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ComboBox<GroupSortBy> sortByBox;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private RadioButton ascRadio;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private RadioButton descRadio;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ToggleGroup orderGroup;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private HBox sortControlGroup;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton catGroupMenuButton;
|
private SplitMenuButton catGroupMenuButton;
|
||||||
|
|
||||||
@ -91,9 +73,6 @@ public class Toolbar extends ToolBar {
|
|||||||
@FXML
|
@FXML
|
||||||
private Label groupByLabel;
|
private Label groupByLabel;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label sortByLabel;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label tagImageViewLabel;
|
private Label tagImageViewLabel;
|
||||||
|
|
||||||
@ -107,20 +86,18 @@ public class Toolbar extends ToolBar {
|
|||||||
|
|
||||||
private final SimpleObjectProperty<SortOrder> orderProperty = new SimpleObjectProperty<>(SortOrder.ASCENDING);
|
private final SimpleObjectProperty<SortOrder> orderProperty = new SimpleObjectProperty<>(SortOrder.ASCENDING);
|
||||||
|
|
||||||
private final InvalidationListener queryInvalidationListener = (Observable o) -> {
|
|
||||||
if (orderGroup.getSelectedToggle() == ascRadio) {
|
|
||||||
orderProperty.set(SortOrder.ASCENDING);
|
|
||||||
} else {
|
|
||||||
orderProperty.set(SortOrder.DESCENDING);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageGalleryController.getDefault().getGroupManager().regroup(groupByBox.getSelectionModel().getSelectedItem(), sortByBox.getSelectionModel().getSelectedItem(), getSortOrder(), false);
|
|
||||||
};
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
private SortChooser<DrawableGroup, GroupSortBy> sortChooser;
|
||||||
|
|
||||||
synchronized public SortOrder getSortOrder() {
|
private final InvalidationListener queryInvalidationListener = new InvalidationListener() {
|
||||||
return orderProperty.get();
|
public void invalidated(Observable o) {
|
||||||
}
|
controller.getGroupManager().regroup(
|
||||||
|
groupByBox.getSelectionModel().getSelectedItem(),
|
||||||
|
sortChooser.getComparator(),
|
||||||
|
sortChooser.getSortOrder(),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public DoubleProperty sizeSliderValue() {
|
public DoubleProperty sizeSliderValue() {
|
||||||
return sizeSlider.valueProperty();
|
return sizeSlider.valueProperty();
|
||||||
@ -135,22 +112,16 @@ public class Toolbar extends ToolBar {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@NbBundle.Messages({"Toolbar.groupByLabel=Group By:",
|
@NbBundle.Messages({"Toolbar.groupByLabel=Group By:",
|
||||||
"Toolbar.sortByLabel=Sort By:",
|
"Toolbar.sortByLabel=Sort By:",
|
||||||
"Toolbar.ascRadio=Ascending",
|
"Toolbar.ascRadio=Ascending",
|
||||||
"Toolbar.descRadio=Descending",
|
"Toolbar.descRadio=Descending",
|
||||||
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
||||||
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
||||||
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"})
|
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
|
||||||
assert catGroupMenuButton != null : "fx:id=\"catSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
assert catGroupMenuButton != null : "fx:id=\"catSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
assert descRadio != null : "fx:id=\"descRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
|
||||||
assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
|
|
||||||
assert orderGroup != null : "fx:id=\"orderGroup\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
|
||||||
assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
|
||||||
assert sortControlGroup != null : "fx:id=\"sortControlGroup\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
|
||||||
assert tagGroupMenuButton != null : "fx:id=\"tagSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
assert tagGroupMenuButton != null : "fx:id=\"tagSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||||
|
|
||||||
controller.viewState().addListener((observable, oldViewState, newViewState) -> {
|
controller.viewState().addListener((observable, oldViewState, newViewState) -> {
|
||||||
@ -174,14 +145,6 @@ public class Toolbar extends ToolBar {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
groupByLabel.setText(Bundle.Toolbar_groupByLabel());
|
|
||||||
sortByLabel.setText(Bundle.Toolbar_sortByLabel());
|
|
||||||
ascRadio.setText(Bundle.Toolbar_ascRadio());
|
|
||||||
descRadio.setText(Bundle.Toolbar_descRadio());
|
|
||||||
tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel());
|
|
||||||
categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel());
|
|
||||||
thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel());
|
|
||||||
|
|
||||||
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(Category.FIVE, controller);
|
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(Category.FIVE, controller);
|
||||||
catGroupMenuButton.setOnAction(cat5GroupAction);
|
catGroupMenuButton.setOnAction(cat5GroupAction);
|
||||||
catGroupMenuButton.setText(cat5GroupAction.getText());
|
catGroupMenuButton.setText(cat5GroupAction.getText());
|
||||||
@ -193,6 +156,12 @@ public class Toolbar extends ToolBar {
|
|||||||
catGroupMenuButton.getItems().setAll(categoryMenues);
|
catGroupMenuButton.getItems().setAll(categoryMenues);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
groupByLabel.setText(Bundle.Toolbar_groupByLabel());
|
||||||
|
tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel());
|
||||||
|
categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel());
|
||||||
|
thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel());
|
||||||
|
|
||||||
groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs()));
|
groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs()));
|
||||||
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
||||||
@ -201,21 +170,20 @@ public class Toolbar extends ToolBar {
|
|||||||
groupByBox.setCellFactory(listView -> new AttributeListCell());
|
groupByBox.setCellFactory(listView -> new AttributeListCell());
|
||||||
groupByBox.setButtonCell(new AttributeListCell());
|
groupByBox.setButtonCell(new AttributeListCell());
|
||||||
|
|
||||||
sortByBox.setCellFactory(listView -> new SortByListCell());
|
sortChooser = new SortChooser<>(GroupSortBy.getValues());
|
||||||
sortByBox.setButtonCell(new SortByListCell());
|
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
||||||
sortByBox.setItems(GroupSortBy.getValues());
|
final boolean orderEnabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY;
|
||||||
|
sortChooser.setSortOrderDisabled(orderEnabled);
|
||||||
sortByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener);
|
|
||||||
|
|
||||||
sortByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
|
||||||
final boolean orderEnabled = newValue == GroupSortBy.NONE || newValue == GroupSortBy.PRIORITY;
|
|
||||||
ascRadio.setDisable(orderEnabled);
|
|
||||||
descRadio.setDisable(orderEnabled);
|
|
||||||
|
|
||||||
|
final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
|
||||||
|
sortChooser.setValueType(valueType);
|
||||||
|
queryInvalidationListener.invalidated(observable);
|
||||||
});
|
});
|
||||||
sortByBox.getSelectionModel().select(GroupSortBy.PRIORITY);
|
|
||||||
|
|
||||||
orderGroup.selectedToggleProperty().addListener(queryInvalidationListener);
|
sortChooser.sortOrderProperty().addListener(queryInvalidationListener);
|
||||||
|
sortChooser.setComparator(GroupSortBy.PRIORITY);
|
||||||
|
getItems().add(1, sortChooser);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncGroupControlsEnabledState(GroupViewState newViewState) {
|
private void syncGroupControlsEnabledState(GroupViewState newViewState) {
|
||||||
@ -230,8 +198,6 @@ public class Toolbar extends ToolBar {
|
|||||||
public void reset() {
|
public void reset() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
||||||
sortByBox.getSelectionModel().select(GroupSortBy.NONE);
|
|
||||||
orderGroup.selectToggle(ascRadio);
|
|
||||||
sizeSlider.setValue(SIZE_SLIDER_DEFAULT);
|
sizeSlider.setValue(SIZE_SLIDER_DEFAULT);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
|||||||
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.AddTagAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
||||||
@ -142,7 +142,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
|||||||
* @param controller the value of controller
|
* @param controller the value of controller
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"DrawableTileBase.menuItem.extractFiles=Extract File(s)",
|
@NbBundle.Messages({"DrawableTileBase.menuItem.extractFiles=Extract File(s)",
|
||||||
"DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"})
|
"DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"})
|
||||||
protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) {
|
protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) {
|
||||||
super(controller);
|
super(controller);
|
||||||
this.groupPane = groupPane;
|
this.groupPane = groupPane;
|
||||||
@ -182,10 +182,8 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
|||||||
private ContextMenu buildContextMenu(DrawableFile<?> file) {
|
private ContextMenu buildContextMenu(DrawableFile<?> file) {
|
||||||
final ArrayList<MenuItem> menuItems = new ArrayList<>();
|
final ArrayList<MenuItem> menuItems = new ArrayList<>();
|
||||||
|
|
||||||
menuItems.add(new CategorizeAction(getController()).getPopupMenu());
|
menuItems.add(CategorizeAction.getCategoriesMenu(getController()));
|
||||||
|
menuItems.add(AddTagAction.getTagMenu(getController()));
|
||||||
menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu());
|
|
||||||
|
|
||||||
|
|
||||||
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
|
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
|
||||||
extractMenuItem.setOnAction(actionEvent -> {
|
extractMenuItem.setOnAction(actionEvent -> {
|
||||||
@ -196,7 +194,6 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
|||||||
});
|
});
|
||||||
menuItems.add(extractMenuItem);
|
menuItems.add(extractMenuItem);
|
||||||
|
|
||||||
|
|
||||||
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
|
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
|
||||||
contentViewer.setOnAction(actionEvent -> {
|
contentViewer.setOnAction(actionEvent -> {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
@ -242,7 +239,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
|||||||
if (followUpToggle.isSelected() == true) {
|
if (followUpToggle.isSelected() == true) {
|
||||||
try {
|
try {
|
||||||
selectionModel.clearAndSelect(file.getId());
|
selectionModel.clearAndSelect(file.getId());
|
||||||
new AddDrawableTagAction(getController()).addTag(getController().getTagsManager().getFollowUpTagName(), "");
|
new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS
|
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -47,6 +48,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
|
|||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.collections.ObservableSet;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@ -110,7 +112,7 @@ import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
|||||||
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.AddTagAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.Back;
|
import org.sleuthkit.autopsy.imagegallery.actions.Back;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeSelectedFilesAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeSelectedFilesAction;
|
||||||
@ -143,39 +145,39 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
|
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
|
||||||
*/
|
*/
|
||||||
public class GroupPane extends BorderPane {
|
public class GroupPane extends BorderPane {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName());
|
||||||
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
|
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
|
||||||
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
|
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
|
||||||
|
|
||||||
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
|
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
|
||||||
|
|
||||||
private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
|
private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
|
||||||
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
|
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
|
||||||
);
|
);
|
||||||
|
|
||||||
private final FileIDSelectionModel selectionModel;
|
private final FileIDSelectionModel selectionModel;
|
||||||
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5,
|
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5,
|
||||||
KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5);
|
KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5);
|
||||||
|
|
||||||
private final Back backAction;
|
private final Back backAction;
|
||||||
|
|
||||||
private final Forward forwardAction;
|
private final Forward forwardAction;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button undoButton;
|
private Button undoButton;
|
||||||
@FXML
|
@FXML
|
||||||
private Button redoButton;
|
private Button redoButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton catSelectedSplitMenu;
|
private SplitMenuButton catSelectedSplitMenu;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton tagSelectedSplitMenu;
|
private SplitMenuButton tagSelectedSplitMenu;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToolBar headerToolBar;
|
private ToolBar headerToolBar;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton cat0Toggle;
|
private ToggleButton cat0Toggle;
|
||||||
@FXML
|
@FXML
|
||||||
@ -188,30 +190,30 @@ public class GroupPane extends BorderPane {
|
|||||||
private ToggleButton cat4Toggle;
|
private ToggleButton cat4Toggle;
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton cat5Toggle;
|
private ToggleButton cat5Toggle;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SegmentedButton segButton;
|
private SegmentedButton segButton;
|
||||||
|
|
||||||
private SlideShowView slideShowPane;
|
private SlideShowView slideShowPane;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton slideShowToggle;
|
private ToggleButton slideShowToggle;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private GridView<Long> gridView;
|
private GridView<Long> gridView;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton tileToggle;
|
private ToggleButton tileToggle;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button nextButton;
|
private Button nextButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button backButton;
|
private Button backButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button forwardButton;
|
private Button forwardButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label groupLabel;
|
private Label groupLabel;
|
||||||
@FXML
|
@FXML
|
||||||
@ -222,24 +224,24 @@ public class GroupPane extends BorderPane {
|
|||||||
private Label catContainerLabel;
|
private Label catContainerLabel;
|
||||||
@FXML
|
@FXML
|
||||||
private Label catHeadingLabel;
|
private Label catHeadingLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private HBox catSegmentedContainer;
|
private HBox catSegmentedContainer;
|
||||||
@FXML
|
@FXML
|
||||||
private HBox catSplitMenuContainer;
|
private HBox catSplitMenuContainer;
|
||||||
|
|
||||||
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
||||||
|
|
||||||
private final NextUnseenGroup nextGroupAction;
|
private final NextUnseenGroup nextGroupAction;
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
private ContextMenu contextMenu;
|
private ContextMenu contextMenu;
|
||||||
|
|
||||||
private Integer selectionAnchorIndex;
|
private Integer selectionAnchorIndex;
|
||||||
private final UndoAction undoAction;
|
private final UndoAction undoAction;
|
||||||
private final RedoAction redoAction;
|
private final RedoAction redoAction;
|
||||||
|
|
||||||
GroupViewMode getGroupViewMode() {
|
GroupViewMode getGroupViewMode() {
|
||||||
return groupViewMode.get();
|
return groupViewMode.get();
|
||||||
}
|
}
|
||||||
@ -262,7 +264,7 @@ public class GroupPane extends BorderPane {
|
|||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private final Map<Long, DrawableCell> cellMap = new HashMap<>();
|
private final Map<Long, DrawableCell> cellMap = new HashMap<>();
|
||||||
|
|
||||||
private final InvalidationListener filesSyncListener = (observable) -> {
|
private final InvalidationListener filesSyncListener = (observable) -> {
|
||||||
final String header = getHeaderString();
|
final String header = getHeaderString();
|
||||||
final List<Long> fileIds = getGroup().getFileIDs();
|
final List<Long> fileIds = getGroup().getFileIDs();
|
||||||
@ -272,7 +274,7 @@ public class GroupPane extends BorderPane {
|
|||||||
groupLabel.setText(header);
|
groupLabel.setText(header);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public GroupPane(ImageGalleryController controller) {
|
public GroupPane(ImageGalleryController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.selectionModel = controller.getSelectionModel();
|
this.selectionModel = controller.getSelectionModel();
|
||||||
@ -281,10 +283,10 @@ public class GroupPane extends BorderPane {
|
|||||||
forwardAction = new Forward(controller);
|
forwardAction = new Forward(controller);
|
||||||
undoAction = new UndoAction(controller);
|
undoAction = new UndoAction(controller);
|
||||||
redoAction = new RedoAction(controller);
|
redoAction = new RedoAction(controller);
|
||||||
|
|
||||||
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
public void activateSlideShowViewer(Long slideShowFileID) {
|
public void activateSlideShowViewer(Long slideShowFileID) {
|
||||||
groupViewMode.set(GroupViewMode.SLIDE_SHOW);
|
groupViewMode.set(GroupViewMode.SLIDE_SHOW);
|
||||||
@ -300,16 +302,16 @@ public class GroupPane extends BorderPane {
|
|||||||
} else {
|
} else {
|
||||||
slideShowPane.setFile(slideShowFileID);
|
slideShowPane.setFile(slideShowFileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCenter(slideShowPane);
|
setCenter(slideShowPane);
|
||||||
slideShowPane.requestFocus();
|
slideShowPane.requestFocus();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncCatToggle(DrawableFile<?> file) {
|
void syncCatToggle(DrawableFile<?> file) {
|
||||||
getToggleForCategory(file.getCategory()).setSelected(true);
|
getToggleForCategory(file.getCategory()).setSelected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activateTileViewer() {
|
public void activateTileViewer() {
|
||||||
groupViewMode.set(GroupViewMode.TILE);
|
groupViewMode.set(GroupViewMode.TILE);
|
||||||
tileToggle.setSelected(true);
|
tileToggle.setSelected(true);
|
||||||
@ -321,11 +323,11 @@ public class GroupPane extends BorderPane {
|
|||||||
slideShowPane = null;
|
slideShowPane = null;
|
||||||
this.scrollToFileID(selectionModel.lastSelectedProperty().get());
|
this.scrollToFileID(selectionModel.lastSelectedProperty().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableGroup getGroup() {
|
public DrawableGroup getGroup() {
|
||||||
return grouping.get();
|
return grouping.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectAllFiles() {
|
private void selectAllFiles() {
|
||||||
selectionModel.clearAndSelectAll(getGroup().getFileIDs());
|
selectionModel.clearAndSelectAll(getGroup().getFileIDs());
|
||||||
}
|
}
|
||||||
@ -342,15 +344,15 @@ public class GroupPane extends BorderPane {
|
|||||||
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
|
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
|
||||||
getGroup().getHashSetHitsCount(), getGroup().getSize());
|
getGroup().getHashSetHitsCount(), getGroup().getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu getContextMenu() {
|
ContextMenu getContextMenu() {
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlyObjectProperty<DrawableGroup> grouping() {
|
ReadOnlyObjectProperty<DrawableGroup> grouping() {
|
||||||
return grouping.getReadOnlyProperty();
|
return grouping.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ToggleButton getToggleForCategory(Category category) {
|
private ToggleButton getToggleForCategory(Category category) {
|
||||||
switch (category) {
|
switch (category) {
|
||||||
case ZERO:
|
case ZERO:
|
||||||
@ -377,10 +379,10 @@ public class GroupPane extends BorderPane {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
@NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)",
|
@NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)",
|
||||||
"GroupPane.bottomLabel.displayText=Group Viewing History: ",
|
"GroupPane.bottomLabel.displayText=Group Viewing History: ",
|
||||||
"GroupPane.hederLabel.displayText=Tag Selected Files:",
|
"GroupPane.hederLabel.displayText=Tag Selected Files:",
|
||||||
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
||||||
"GroupPane.catHeadingLabel.displayText=Category:"})
|
"GroupPane.catHeadingLabel.displayText=Category:"})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||||
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||||
@ -395,7 +397,7 @@ public class GroupPane extends BorderPane {
|
|||||||
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||||
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||||
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||||
|
|
||||||
for (Category cat : Category.values()) {
|
for (Category cat : Category.values()) {
|
||||||
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
||||||
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
|
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
|
||||||
@ -403,9 +405,9 @@ public class GroupPane extends BorderPane {
|
|||||||
toggleForCategory.getStyleClass().add("toggle-button");
|
toggleForCategory.getStyleClass().add("toggle-button");
|
||||||
toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> {
|
toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> {
|
||||||
if (toggleSelected && slideShowPane != null) {
|
if (toggleSelected && slideShowPane != null) {
|
||||||
slideShowPane.getFileID().ifPresent((fileID) -> {
|
slideShowPane.getFileID().ifPresent(fileID -> {
|
||||||
selectionModel.clearAndSelect(fileID);
|
selectionModel.clearAndSelect(fileID);
|
||||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), "");
|
new CategorizeAction(controller, cat, ImmutableSet.of(fileID)).handle(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -420,11 +422,11 @@ public class GroupPane extends BorderPane {
|
|||||||
gridView.cellHeightProperty().bind(cellSize);
|
gridView.cellHeightProperty().bind(cellSize);
|
||||||
gridView.cellWidthProperty().bind(cellSize);
|
gridView.cellWidthProperty().bind(cellSize);
|
||||||
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
|
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
|
||||||
|
|
||||||
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
|
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
|
||||||
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
||||||
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
try {
|
try {
|
||||||
TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller);
|
TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller);
|
||||||
@ -441,9 +443,9 @@ public class GroupPane extends BorderPane {
|
|||||||
tagSelectedSplitMenu.getItems().setAll(selTagMenues);
|
tagSelectedSplitMenu.getItems().setAll(selTagMenues);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller);
|
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller);
|
||||||
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
|
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
|
||||||
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
|
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
|
||||||
@ -455,12 +457,12 @@ public class GroupPane extends BorderPane {
|
|||||||
catSelectedSplitMenu.getItems().setAll(categoryMenues);
|
catSelectedSplitMenu.getItems().setAll(categoryMenues);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
slideShowToggle.getStyleClass().remove("radio-button");
|
slideShowToggle.getStyleClass().remove("radio-button");
|
||||||
slideShowToggle.getStyleClass().add("toggle-button");
|
slideShowToggle.getStyleClass().add("toggle-button");
|
||||||
tileToggle.getStyleClass().remove("radio-button");
|
tileToggle.getStyleClass().remove("radio-button");
|
||||||
tileToggle.getStyleClass().add("toggle-button");
|
tileToggle.getStyleClass().add("toggle-button");
|
||||||
|
|
||||||
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
|
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
|
||||||
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
|
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
|
||||||
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
|
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
|
||||||
@ -480,12 +482,12 @@ public class GroupPane extends BorderPane {
|
|||||||
//listen to toggles and update view state
|
//listen to toggles and update view state
|
||||||
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
|
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
|
||||||
tileToggle.setOnAction(onAction -> activateTileViewer());
|
tileToggle.setOnAction(onAction -> activateTileViewer());
|
||||||
|
|
||||||
controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState));
|
controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState));
|
||||||
|
|
||||||
addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler);
|
addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler);
|
||||||
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
|
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
|
||||||
|
|
||||||
ActionUtils.configureButton(undoAction, undoButton);
|
ActionUtils.configureButton(undoAction, undoButton);
|
||||||
ActionUtils.configureButton(redoAction, redoButton);
|
ActionUtils.configureButton(redoAction, redoButton);
|
||||||
ActionUtils.configureButton(forwardAction, forwardButton);
|
ActionUtils.configureButton(forwardAction, forwardButton);
|
||||||
@ -501,7 +503,7 @@ public class GroupPane extends BorderPane {
|
|||||||
nextButton.setEffect(null);
|
nextButton.setEffect(null);
|
||||||
onAction.handle(actionEvent);
|
onAction.handle(actionEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
nextGroupAction.disabledProperty().addListener((Observable observable) -> {
|
nextGroupAction.disabledProperty().addListener((Observable observable) -> {
|
||||||
boolean newValue = nextGroupAction.isDisabled();
|
boolean newValue = nextGroupAction.isDisabled();
|
||||||
nextButton.setEffect(newValue ? null : DROP_SHADOW);
|
nextButton.setEffect(newValue ? null : DROP_SHADOW);
|
||||||
@ -521,7 +523,7 @@ public class GroupPane extends BorderPane {
|
|||||||
scrollToFileID(newFileId);
|
scrollToFileID(newFileId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setViewState(controller.viewState().get());
|
setViewState(controller.viewState().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,16 +533,16 @@ public class GroupPane extends BorderPane {
|
|||||||
if (newFileID == null) {
|
if (newFileID == null) {
|
||||||
return; //scrolling to no file doesn't make sense, so abort.
|
return; //scrolling to no file doesn't make sense, so abort.
|
||||||
}
|
}
|
||||||
|
|
||||||
final ObservableList<Long> fileIds = gridView.getItems();
|
final ObservableList<Long> fileIds = gridView.getItems();
|
||||||
|
|
||||||
int selectedIndex = fileIds.indexOf(newFileID);
|
int selectedIndex = fileIds.indexOf(newFileID);
|
||||||
if (selectedIndex == -1) {
|
if (selectedIndex == -1) {
|
||||||
//somehow we got passed a file id that isn't in the curent group.
|
//somehow we got passed a file id that isn't in the curent group.
|
||||||
//this should never happen, but if it does everything is going to fail, so abort.
|
//this should never happen, but if it does everything is going to fail, so abort.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getScrollBar().ifPresent(scrollBar -> {
|
getScrollBar().ifPresent(scrollBar -> {
|
||||||
DrawableCell cell = cellMap.get(newFileID);
|
DrawableCell cell = cellMap.get(newFileID);
|
||||||
|
|
||||||
@ -567,14 +569,14 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
cell = cellMap.get(newFileID);
|
cell = cellMap.get(newFileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
|
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
|
||||||
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
|
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
|
||||||
|
|
||||||
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
|
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
|
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
|
||||||
|
|
||||||
if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
|
if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
|
||||||
scrollBar.decrement();
|
scrollBar.decrement();
|
||||||
} else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) {
|
} else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) {
|
||||||
@ -592,13 +594,13 @@ public class GroupPane extends BorderPane {
|
|||||||
* @param grouping the new grouping assigned to this group
|
* @param grouping the new grouping assigned to this group
|
||||||
*/
|
*/
|
||||||
void setViewState(GroupViewState viewState) {
|
void setViewState(GroupViewState viewState) {
|
||||||
|
|
||||||
if (isNull(viewState) || isNull(viewState.getGroup())) {
|
if (isNull(viewState) || isNull(viewState.getGroup())) {
|
||||||
if (nonNull(getGroup())) {
|
if (nonNull(getGroup())) {
|
||||||
getGroup().getFileIDs().removeListener(filesSyncListener);
|
getGroup().getFileIDs().removeListener(filesSyncListener);
|
||||||
}
|
}
|
||||||
this.grouping.set(null);
|
this.grouping.set(null);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
gridView.getItems().setAll(Collections.emptyList());
|
gridView.getItems().setAll(Collections.emptyList());
|
||||||
setCenter(null);
|
setCenter(null);
|
||||||
@ -610,18 +612,18 @@ public class GroupPane extends BorderPane {
|
|||||||
cellMap.clear();
|
cellMap.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (getGroup() != viewState.getGroup()) {
|
if (getGroup() != viewState.getGroup()) {
|
||||||
if (nonNull(getGroup())) {
|
if (nonNull(getGroup())) {
|
||||||
getGroup().getFileIDs().removeListener(filesSyncListener);
|
getGroup().getFileIDs().removeListener(filesSyncListener);
|
||||||
}
|
}
|
||||||
this.grouping.set(viewState.getGroup());
|
this.grouping.set(viewState.getGroup());
|
||||||
|
|
||||||
getGroup().getFileIDs().addListener(filesSyncListener);
|
getGroup().getFileIDs().addListener(filesSyncListener);
|
||||||
|
|
||||||
final String header = getHeaderString();
|
final String header = getHeaderString();
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
gridView.getItems().setAll(getGroup().getFileIDs());
|
gridView.getItems().setAll(getGroup().getFileIDs());
|
||||||
slideShowToggle.setDisable(gridView.getItems().isEmpty());
|
slideShowToggle.setDisable(gridView.getItems().isEmpty());
|
||||||
@ -636,14 +638,14 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private void resetScrollBar() {
|
private void resetScrollBar() {
|
||||||
getScrollBar().ifPresent((scrollBar) -> {
|
getScrollBar().ifPresent((scrollBar) -> {
|
||||||
scrollBar.setValue(0);
|
scrollBar.setValue(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private Optional<ScrollBar> getScrollBar() {
|
private Optional<ScrollBar> getScrollBar() {
|
||||||
if (gridView == null || gridView.getSkin() == null) {
|
if (gridView == null || gridView.getSkin() == null) {
|
||||||
@ -651,16 +653,16 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS
|
return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
void makeSelection(Boolean shiftDown, Long newFileID) {
|
void makeSelection(Boolean shiftDown, Long newFileID) {
|
||||||
|
|
||||||
if (shiftDown) {
|
if (shiftDown) {
|
||||||
//TODO: do more hear to implement slicker multiselect
|
//TODO: do more hear to implement slicker multiselect
|
||||||
int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
|
int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
|
||||||
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
|
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
|
||||||
endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt();
|
endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt();
|
||||||
List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1);
|
List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1);
|
||||||
|
|
||||||
selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()]));
|
selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()]));
|
||||||
selectionModel.select(newFileID);
|
selectionModel.select(newFileID);
|
||||||
} else {
|
} else {
|
||||||
@ -668,11 +670,11 @@ public class GroupPane extends BorderPane {
|
|||||||
selectionModel.clearAndSelect(newFileID);
|
selectionModel.clearAndSelect(newFileID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DrawableCell extends GridCell<Long> {
|
private class DrawableCell extends GridCell<Long> {
|
||||||
|
|
||||||
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
|
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
|
||||||
|
|
||||||
DrawableCell() {
|
DrawableCell() {
|
||||||
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
|
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
|
||||||
if (oldValue != null) {
|
if (oldValue != null) {
|
||||||
@ -688,19 +690,19 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cellMap.put(newValue, DrawableCell.this);
|
cellMap.put(newValue, DrawableCell.this);
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setGraphic(tile);
|
setGraphic(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Long item, boolean empty) {
|
protected void updateItem(Long item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
tile.setFile(item);
|
tile.setFile(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetItem() {
|
void resetItem() {
|
||||||
tile.setFile(null);
|
tile.setFile(null);
|
||||||
}
|
}
|
||||||
@ -711,10 +713,10 @@ public class GroupPane extends BorderPane {
|
|||||||
* arrows)
|
* arrows)
|
||||||
*/
|
*/
|
||||||
private class KeyboardHandler implements EventHandler<KeyEvent> {
|
private class KeyboardHandler implements EventHandler<KeyEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(KeyEvent t) {
|
public void handle(KeyEvent t) {
|
||||||
|
|
||||||
if (t.getEventType() == KeyEvent.KEY_PRESSED) {
|
if (t.getEventType() == KeyEvent.KEY_PRESSED) {
|
||||||
switch (t.getCode()) {
|
switch (t.getCode()) {
|
||||||
case SHIFT:
|
case SHIFT:
|
||||||
@ -757,51 +759,56 @@ public class GroupPane extends BorderPane {
|
|||||||
t.consume();
|
t.consume();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
|
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
|
||||||
selectAllFiles();
|
selectAllFiles();
|
||||||
t.consume();
|
t.consume();
|
||||||
}
|
}
|
||||||
if (selectionModel.getSelected().isEmpty() == false) {
|
ObservableSet<Long> selected = selectionModel.getSelected();
|
||||||
switch (t.getCode()) {
|
if (selected.isEmpty() == false) {
|
||||||
case NUMPAD0:
|
Category cat = keyCodeToCat(t.getCode());
|
||||||
case DIGIT0:
|
if (cat != null) {
|
||||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ZERO), "");
|
new CategorizeAction(controller, cat, selected).handle(null);
|
||||||
break;
|
|
||||||
case NUMPAD1:
|
|
||||||
case DIGIT1:
|
|
||||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ONE), "");
|
|
||||||
break;
|
|
||||||
case NUMPAD2:
|
|
||||||
case DIGIT2:
|
|
||||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.TWO), "");
|
|
||||||
break;
|
|
||||||
case NUMPAD3:
|
|
||||||
case DIGIT3:
|
|
||||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.THREE), "");
|
|
||||||
break;
|
|
||||||
case NUMPAD4:
|
|
||||||
case DIGIT4:
|
|
||||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FOUR), "");
|
|
||||||
break;
|
|
||||||
case NUMPAD5:
|
|
||||||
case DIGIT5:
|
|
||||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FIVE), "");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Category keyCodeToCat(KeyCode t) {
|
||||||
|
if (t != null) {
|
||||||
|
switch (t) {
|
||||||
|
case NUMPAD0:
|
||||||
|
case DIGIT0:
|
||||||
|
return Category.ZERO;
|
||||||
|
case NUMPAD1:
|
||||||
|
case DIGIT1:
|
||||||
|
return Category.ONE;
|
||||||
|
case NUMPAD2:
|
||||||
|
case DIGIT2:
|
||||||
|
return Category.TWO;
|
||||||
|
case NUMPAD3:
|
||||||
|
case DIGIT3:
|
||||||
|
return Category.THREE;
|
||||||
|
case NUMPAD4:
|
||||||
|
case DIGIT4:
|
||||||
|
return Category.FOUR;
|
||||||
|
case NUMPAD5:
|
||||||
|
case DIGIT5:
|
||||||
|
return Category.FIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleArrows(KeyEvent t) {
|
private void handleArrows(KeyEvent t) {
|
||||||
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
|
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
|
||||||
|
|
||||||
int lastSelectedIndex = lastSelectFileId != null
|
int lastSelectedIndex = lastSelectFileId != null
|
||||||
? grouping.get().getFileIDs().indexOf(lastSelectFileId)
|
? grouping.get().getFileIDs().indexOf(lastSelectFileId)
|
||||||
: Optional.ofNullable(selectionAnchorIndex).orElse(0);
|
: Optional.ofNullable(selectionAnchorIndex).orElse(0);
|
||||||
|
|
||||||
final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1);
|
final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1);
|
||||||
|
|
||||||
final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1);
|
final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1);
|
||||||
|
|
||||||
// implement proper keyboard based multiselect
|
// implement proper keyboard based multiselect
|
||||||
@ -820,17 +827,19 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MouseHandler implements EventHandler<MouseEvent> {
|
private class MouseHandler implements EventHandler<MouseEvent> {
|
||||||
|
|
||||||
private ContextMenu buildContextMenu() {
|
private ContextMenu buildContextMenu() {
|
||||||
ArrayList<MenuItem> menuItems = new ArrayList<>();
|
ArrayList<MenuItem> menuItems = new ArrayList<>();
|
||||||
|
|
||||||
menuItems.add(new CategorizeAction(controller).getPopupMenu());
|
|
||||||
menuItems.add(new AddDrawableTagAction(controller).getPopupMenu());
|
menuItems.add(CategorizeAction.getCategoriesMenu(controller));
|
||||||
|
menuItems.add(AddTagAction.getTagMenu(controller));
|
||||||
|
|
||||||
|
|
||||||
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
|
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
|
||||||
|
|
||||||
for (ContextMenuActionsProvider provider : menuProviders) {
|
for (ContextMenuActionsProvider provider : menuProviders) {
|
||||||
for (final Action act : provider.getActions()) {
|
for (final Action act : provider.getActions()) {
|
||||||
if (act instanceof Presenter.Popup) {
|
if (act instanceof Presenter.Popup) {
|
||||||
@ -847,12 +856,12 @@ public class GroupPane extends BorderPane {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
menuItems.add(extractMenuItem);
|
menuItems.add(extractMenuItem);
|
||||||
|
|
||||||
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
|
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
|
||||||
contextMenu.setAutoHide(true);
|
contextMenu.setAutoHide(true);
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MouseEvent t) {
|
public void handle(MouseEvent t) {
|
||||||
switch (t.getButton()) {
|
switch (t.getButton()) {
|
||||||
@ -873,7 +882,7 @@ public class GroupPane extends BorderPane {
|
|||||||
if (contextMenu == null) {
|
if (contextMenu == null) {
|
||||||
contextMenu = buildContextMenu();
|
contextMenu = buildContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenu.hide();
|
contextMenu.hide();
|
||||||
contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY());
|
contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY());
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.groupTreeCell{
|
.groupCell{
|
||||||
|
|
||||||
-fx-indent:5; /* default indent is 10 */
|
-fx-indent:5; /* default indent is 10 */
|
||||||
}
|
}
|
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* 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 static java.util.Objects.isNull;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.InvalidationListener;
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Cell;
|
||||||
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.Labeled;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.OverrunStyle;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.control.TreeCell;
|
||||||
|
import javafx.scene.control.TreeView;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Factory for Cells to use in a ListView<DrawableGroup> or
|
||||||
|
* TreeView<GroupTreeNode>
|
||||||
|
*/
|
||||||
|
class GroupCellFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* icon to use if a cell doesn't represent a group but just a folder(with no
|
||||||
|
* DrawableFiles) in the file system hierarchy.
|
||||||
|
*/
|
||||||
|
private static final Image EMPTY_FOLDER_ICON = new Image("/org/sleuthkit/autopsy/imagegallery/images/folder.png"); //NON-NLS
|
||||||
|
|
||||||
|
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
|
||||||
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
|
GroupCellFactory(ImageGalleryController controller, ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
|
||||||
|
this.controller = controller;
|
||||||
|
this.sortOrder = sortOrderProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupListCell getListCell(ListView<DrawableGroup> listview) {
|
||||||
|
return initCell(new GroupListCell());
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupTreeCell getTreeCell(TreeView<?> treeView) {
|
||||||
|
return initCell(new GroupTreeCell());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove the listener when it is not needed any more
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
* @param oldGroup
|
||||||
|
*/
|
||||||
|
private void removeListeners(InvalidationListener listener, DrawableGroup oldGroup) {
|
||||||
|
sortOrder.removeListener(listener);
|
||||||
|
oldGroup.getFileIDs().removeListener(listener);
|
||||||
|
oldGroup.seenProperty().removeListener(listener);
|
||||||
|
oldGroup.uncatCountProperty().removeListener(listener);
|
||||||
|
oldGroup.hashSetHitsCountProperty().removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addListeners(InvalidationListener listener, DrawableGroup group) {
|
||||||
|
//if the sort order changes, update the counts displayed to match the sorted by property
|
||||||
|
sortOrder.addListener(listener);
|
||||||
|
//if number of files in this group changes (eg a file is recategorized), update counts via listener
|
||||||
|
group.getFileIDs().addListener(listener);
|
||||||
|
group.uncatCountProperty().addListener(listener);
|
||||||
|
group.hashSetHitsCountProperty().addListener(listener);
|
||||||
|
//if the seen state of this group changes update its style
|
||||||
|
group.seenProperty().addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <X extends Cell<?> & GroupCell<?>> X initCell(X cell) {
|
||||||
|
/*
|
||||||
|
* reduce indent of TreeCells to 5, default is 10 which uses up a lot of
|
||||||
|
* space. Define seen and unseen styles
|
||||||
|
*/
|
||||||
|
cell.getStylesheets().add(GroupCellFactory.class.getResource("GroupCell.css").toExternalForm()); //NON-NLS
|
||||||
|
cell.getStyleClass().add("groupCell"); //NON-NLS
|
||||||
|
|
||||||
|
//since end of path is probably more interesting put ellipsis at front
|
||||||
|
cell.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
|
||||||
|
|
||||||
|
Platform.runLater(() -> cell.prefWidthProperty().bind(cell.getView().widthProperty().subtract(15)));
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <X extends Cell<?> & GroupCell<?>> void updateGroup(X cell, DrawableGroup group) {
|
||||||
|
addListeners(cell.getGroupListener(), group);
|
||||||
|
|
||||||
|
//and use icon corresponding to group type
|
||||||
|
final Node graphic = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
|
||||||
|
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
|
||||||
|
: group.getGroupKey().getGraphic();
|
||||||
|
final String text = getCellText(cell);
|
||||||
|
final String style = getSeenStyleClass(cell);
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cell.setTooltip(new Tooltip(text));
|
||||||
|
cell.setGraphic(graphic);
|
||||||
|
cell.setText(text);
|
||||||
|
cell.setStyle(style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private <X extends Labeled & GroupCell<?>> void clearCell(X cell) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cell.setTooltip(null);
|
||||||
|
cell.setText(null);
|
||||||
|
cell.setGraphic(null);
|
||||||
|
cell.setStyle("");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the styleClass to apply based on the assigned group's seen status
|
||||||
|
*
|
||||||
|
* @return the style class to apply
|
||||||
|
*/
|
||||||
|
private String getSeenStyleClass(GroupCell<?> cell) {
|
||||||
|
return cell.getGroup()
|
||||||
|
.map(DrawableGroup::isSeen)
|
||||||
|
.map(seen -> seen ? "" : "-fx-font-weight:bold;") //NON-NLS
|
||||||
|
.orElse(""); //if item is null or group is null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the counts part of the text to apply to this cell, including
|
||||||
|
* parentheses
|
||||||
|
*
|
||||||
|
* @return get the counts part of the text to apply to this cell
|
||||||
|
*/
|
||||||
|
private String getCountsText(GroupCell<?> cell) {
|
||||||
|
return cell.getGroup()
|
||||||
|
.map(group ->
|
||||||
|
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
|
||||||
|
? group.getSize()
|
||||||
|
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
|
||||||
|
).orElse(""); //if item is null or group is null
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCellText(GroupCell<?> cell) {
|
||||||
|
return cell.getGroupName() + getCountsText(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GroupTreeCell extends TreeCell<GroupTreeNode> implements GroupCell<TreeView<GroupTreeNode>> {
|
||||||
|
|
||||||
|
private final InvalidationListener groupListener = new GroupListener<>(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reference to group files listener that allows us to remove it from a
|
||||||
|
* group when a new group is assigned to this Cell
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InvalidationListener getGroupListener() {
|
||||||
|
return groupListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeView<GroupTreeNode> getView() {
|
||||||
|
return getTreeView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGroupName() {
|
||||||
|
return Optional.ofNullable(getItem())
|
||||||
|
.map(treeNode -> StringUtils.defaultIfBlank(treeNode.getPath(), DrawableGroup.getBlankGroupName()))
|
||||||
|
.orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DrawableGroup> getGroup() {
|
||||||
|
return Optional.ofNullable(getItem())
|
||||||
|
.map(GroupTreeNode::getGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void updateItem(final GroupTreeNode newItem, boolean empty) {
|
||||||
|
//if there was a previous group, remove the listeners
|
||||||
|
getGroup().ifPresent(oldGroup -> removeListeners(getGroupListener(), oldGroup));
|
||||||
|
|
||||||
|
super.updateItem(newItem, empty);
|
||||||
|
|
||||||
|
if (isNull(newItem) || empty) {
|
||||||
|
clearCell(this);
|
||||||
|
} else {
|
||||||
|
DrawableGroup newGroup = newItem.getGroup();
|
||||||
|
if (isNull(newGroup)) {
|
||||||
|
//this cod epath should only be invoked for non-group Tree
|
||||||
|
final String groupName = getGroupName();
|
||||||
|
//"dummy" group in file system tree <=> a folder with no drawables
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
setTooltip(new Tooltip(groupName));
|
||||||
|
setText(groupName);
|
||||||
|
setGraphic(new ImageView(EMPTY_FOLDER_ICON));
|
||||||
|
setStyle("");
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
updateGroup(this, newGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GroupListCell extends ListCell<DrawableGroup> implements GroupCell<ListView<DrawableGroup>> {
|
||||||
|
|
||||||
|
private final InvalidationListener groupListener = new GroupListener<>(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reference to group files listener that allows us to remove it from a
|
||||||
|
* group when a new group is assigned to this Cell
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InvalidationListener getGroupListener() {
|
||||||
|
return groupListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListView<DrawableGroup> getView() {
|
||||||
|
return getListView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGroupName() {
|
||||||
|
return Optional.ofNullable(getItem())
|
||||||
|
.map(group -> group.getGroupByValueDislpayName())
|
||||||
|
.orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DrawableGroup> getGroup() {
|
||||||
|
return Optional.ofNullable(getItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void updateItem(final DrawableGroup newGroup, boolean empty) {
|
||||||
|
//if there was a previous group, remove the listeners
|
||||||
|
getGroup().ifPresent(oldGroup -> removeListeners(getGroupListener(), oldGroup));
|
||||||
|
|
||||||
|
super.updateItem(newGroup, empty);
|
||||||
|
|
||||||
|
if (isNull(newGroup) || empty) {
|
||||||
|
clearCell(this);
|
||||||
|
} else {
|
||||||
|
updateGroup(this, newGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface GroupCell<X extends Control> {
|
||||||
|
|
||||||
|
String getGroupName();
|
||||||
|
|
||||||
|
X getView();
|
||||||
|
|
||||||
|
Optional<DrawableGroup> getGroup();
|
||||||
|
|
||||||
|
InvalidationListener getGroupListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GroupListener<X extends Labeled & GroupCell<?>> implements InvalidationListener {
|
||||||
|
|
||||||
|
private final X cell;
|
||||||
|
|
||||||
|
GroupListener(X cell) {
|
||||||
|
this.cell = cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidated(Observable o) {
|
||||||
|
final String text = getCellText(cell);
|
||||||
|
final String style = getSeenStyleClass(cell);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cell.setText(text);
|
||||||
|
cell.setTooltip(new Tooltip(text));
|
||||||
|
cell.setStyle(style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,21 +18,18 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@NbBundle.Messages({"GroupComparators.uncategorizedCount=Uncategorized Count",
|
@NbBundle.Messages({"GroupComparators.uncategorizedCount=Uncategorized Count",
|
||||||
"GroupComparators.groupName=Group Name",
|
"GroupComparators.groupName=Group Name",
|
||||||
"GroupComparators.hitCount=Hit Count",
|
"GroupComparators.hitCount=Hit Count",
|
||||||
"GroupComparators.groupSize=Group Size",
|
"GroupComparators.groupSize=Group Size",
|
||||||
"GroupComparators.hitDensity=Hit Density"})
|
"GroupComparators.hitDensity=Hit Density"})
|
||||||
final class GroupComparators<T extends Comparable<T>> implements Comparator<DrawableGroup> {
|
final class GroupComparators<T extends Comparable<T>> implements Comparator<DrawableGroup> {
|
||||||
|
|
||||||
static final GroupComparators<Long> UNCATEGORIZED_COUNT =
|
static final GroupComparators<Long> UNCATEGORIZED_COUNT =
|
||||||
@ -50,10 +47,10 @@ final class GroupComparators<T extends Comparable<T>> implements Comparator<Draw
|
|||||||
static final GroupComparators<Double> HIT_FILE_RATIO =
|
static final GroupComparators<Double> HIT_FILE_RATIO =
|
||||||
new GroupComparators<>(Bundle.GroupComparators_hitDensity(), DrawableGroup::getHashHitDensity, density -> String.format("%.2f", density) + "%", true); //NON-NLS
|
new GroupComparators<>(Bundle.GroupComparators_hitDensity(), DrawableGroup::getHashHitDensity, density -> String.format("%.2f", density) + "%", true); //NON-NLS
|
||||||
|
|
||||||
private final static ImmutableList<GroupComparators<?>> values = ImmutableList.of(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO);
|
private final static ObservableList<GroupComparators<?>> values = FXCollections.observableArrayList(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO);
|
||||||
|
|
||||||
public static ImmutableList<GroupComparators<?>> getValues() {
|
public static ObservableList<GroupComparators<?>> getValues() {
|
||||||
return values;
|
return FXCollections.unmodifiableObservableList(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Function<DrawableGroup, T> extractor;
|
private final Function<DrawableGroup, T> extractor;
|
||||||
|
@ -1,174 +0,0 @@
|
|||||||
/*
|
|
||||||
* Autopsy Forensic Browser
|
|
||||||
*
|
|
||||||
* Copyright 2015-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.imagegallery.gui.navpanel;
|
|
||||||
|
|
||||||
import static java.util.Objects.isNull;
|
|
||||||
import java.util.Optional;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.InvalidationListener;
|
|
||||||
import javafx.beans.Observable;
|
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.ListCell;
|
|
||||||
import javafx.scene.control.OverrunStyle;
|
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
|
||||||
import org.sleuthkit.datamodel.TagName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class GroupListCell extends ListCell<DrawableGroup> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* icon to use if this cell's TreeNode doesn't represent a group but just a
|
|
||||||
* folder(with no DrawableFiles) in the file system hierarchy.
|
|
||||||
*/
|
|
||||||
private static final Image EMPTY_FOLDER_ICON =
|
|
||||||
new Image(GroupTreeCell.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/folder.png")); //NON-NLS
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reference to group files listener that allows us to remove it from a
|
|
||||||
* group when a new group is assigned to this Cell
|
|
||||||
*/
|
|
||||||
private final InvalidationListener fileCountListener = (Observable o) -> {
|
|
||||||
final String text = getGroupName() + getCountsText();
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setText(text);
|
|
||||||
setTooltip(new Tooltip(text));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reference to group seen listener that allows us to remove it from a group
|
|
||||||
* when a new group is assigned to this Cell
|
|
||||||
*/
|
|
||||||
private final InvalidationListener seenListener = (Observable o) -> {
|
|
||||||
final String style = getSeenStyleClass();
|
|
||||||
Platform.runLater(() -> setStyle(style));
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
|
|
||||||
private final ImageGalleryController controller;
|
|
||||||
|
|
||||||
GroupListCell(ImageGalleryController controller, ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
|
|
||||||
this.controller = controller;
|
|
||||||
this.sortOrder = sortOrderProperty;
|
|
||||||
getStylesheets().add(GroupTreeCell.class.getResource("GroupTreeCell.css").toExternalForm()); //NON-NLS
|
|
||||||
getStyleClass().add("groupTreeCell"); //reduce indent to 5, default is 10 which uses up a lot of space. NON-NLS
|
|
||||||
|
|
||||||
//since end of path is probably more interesting put ellipsis at front
|
|
||||||
setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
|
|
||||||
Platform.runLater(() -> prefWidthProperty().bind(getListView().widthProperty().subtract(15)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected synchronized void updateItem(final DrawableGroup group, boolean empty) {
|
|
||||||
//if there was a previous group, remove the listeners
|
|
||||||
Optional.ofNullable(getItem())
|
|
||||||
.ifPresent(oldGroup -> {
|
|
||||||
sortOrder.removeListener(fileCountListener);
|
|
||||||
oldGroup.getFileIDs().removeListener(fileCountListener);
|
|
||||||
oldGroup.seenProperty().removeListener(seenListener);
|
|
||||||
oldGroup.uncatCountProperty().removeListener(fileCountListener);
|
|
||||||
oldGroup.hashSetHitsCountProperty().removeListener(fileCountListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
super.updateItem(group, empty);
|
|
||||||
|
|
||||||
if (isNull(group) || empty) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setTooltip(null);
|
|
||||||
setText(null);
|
|
||||||
setGraphic(null);
|
|
||||||
setStyle("");
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
final String text = getGroupName() + getCountsText();
|
|
||||||
String style;
|
|
||||||
Node icon;
|
|
||||||
if (isNull(group)) {
|
|
||||||
//"dummy" group in file system tree <=> a folder with no drawables
|
|
||||||
icon = new ImageView(EMPTY_FOLDER_ICON);
|
|
||||||
style = "";
|
|
||||||
} else {
|
|
||||||
//if number of files in this group changes (eg a file is recategorized), update counts via listener
|
|
||||||
group.getFileIDs().addListener(fileCountListener);
|
|
||||||
group.uncatCountProperty().addListener(fileCountListener);
|
|
||||||
group.hashSetHitsCountProperty().addListener(fileCountListener);
|
|
||||||
sortOrder.addListener(fileCountListener);
|
|
||||||
//if the seen state of this group changes update its style
|
|
||||||
group.seenProperty().addListener(seenListener);
|
|
||||||
|
|
||||||
//and use icon corresponding to group type
|
|
||||||
icon = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
|
|
||||||
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
|
|
||||||
: group.getGroupKey().getGraphic();
|
|
||||||
style = getSeenStyleClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setTooltip(new Tooltip(text));
|
|
||||||
setGraphic(icon);
|
|
||||||
setText(text);
|
|
||||||
setStyle(style);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getGroupName() {
|
|
||||||
return Optional.ofNullable(getItem())
|
|
||||||
.map(group -> group.getGroupByValueDislpayName())
|
|
||||||
.orElse("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return the styleClass to apply based on the assigned group's seen status
|
|
||||||
*
|
|
||||||
* @return the style class to apply
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
private String getSeenStyleClass() {
|
|
||||||
return Optional.ofNullable(getItem())
|
|
||||||
.map(DrawableGroup::isSeen)
|
|
||||||
.map(seen -> seen ? "" : "-fx-font-weight:bold;") //NON-NLS
|
|
||||||
.orElse(""); //if item is null or group is null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the counts part of the text to apply to this cell, including
|
|
||||||
* parentheses
|
|
||||||
*
|
|
||||||
* @return get the counts part of the text to apply to this cell
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
private String getCountsText() {
|
|
||||||
return Optional.ofNullable(getItem())
|
|
||||||
.map(group ->
|
|
||||||
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
|
|
||||||
? group.getSize()
|
|
||||||
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
|
|
||||||
).orElse(""); //if item is null or group is null
|
|
||||||
}
|
|
||||||
}
|
|
@ -72,7 +72,8 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
|
|||||||
getToolBar().visibleProperty().bind(groupedByPath.not());
|
getToolBar().visibleProperty().bind(groupedByPath.not());
|
||||||
getToolBar().managedProperty().bind(groupedByPath.not());
|
getToolBar().managedProperty().bind(groupedByPath.not());
|
||||||
|
|
||||||
groupTree.setCellFactory(treeView -> new GroupTreeCell(getController(), getSortByBox().getSelectionModel().selectedItemProperty()));
|
GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), comparatorProperty());
|
||||||
|
groupTree.setCellFactory(groupCellFactory::getTreeCell);
|
||||||
groupTree.setShowRoot(false);
|
groupTree.setShowRoot(false);
|
||||||
|
|
||||||
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
||||||
@ -151,4 +152,5 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
|
|||||||
return Arrays.asList(stripStart);
|
return Arrays.asList(stripStart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.imagegallery.gui.navpanel;
|
|
||||||
|
|
||||||
import static java.util.Objects.isNull;
|
|
||||||
import java.util.Optional;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.InvalidationListener;
|
|
||||||
import javafx.beans.Observable;
|
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.OverrunStyle;
|
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import javafx.scene.control.TreeCell;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
|
||||||
import org.sleuthkit.datamodel.TagName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cell in the NavPanel tree that listens to its associated group's fileids
|
|
||||||
* and seen status,and updates GUI to reflect them.
|
|
||||||
*
|
|
||||||
* TODO: we should use getStyleClass().add() rather than setStyle but it didn't
|
|
||||||
* seem to work properly
|
|
||||||
*/
|
|
||||||
class GroupTreeCell extends TreeCell<GroupTreeNode> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* icon to use if this cell's TreeNode doesn't represent a group but just a
|
|
||||||
* folder(with no DrawableFiles) in the file system hierarchy.
|
|
||||||
*/
|
|
||||||
private static final Image EMPTY_FOLDER_ICON =
|
|
||||||
new Image(GroupTreeCell.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/folder.png")); //NON-NLS
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reference to group files listener that allows us to remove it from a
|
|
||||||
* group when a new group is assigned to this Cell
|
|
||||||
*/
|
|
||||||
private final InvalidationListener fileCountListener = (Observable o) -> {
|
|
||||||
final String text = getGroupName() + getCountsText();
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setText(text);
|
|
||||||
setTooltip(new Tooltip(text));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reference to group seen listener that allows us to remove it from a group
|
|
||||||
* when a new group is assigned to this Cell
|
|
||||||
*/
|
|
||||||
private final InvalidationListener seenListener = (Observable o) -> {
|
|
||||||
final String style = getSeenStyleClass();
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setStyle(style);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
|
|
||||||
private final ImageGalleryController controller;
|
|
||||||
|
|
||||||
GroupTreeCell(ImageGalleryController controller, ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
|
|
||||||
this.controller = controller;
|
|
||||||
this.sortOrder = sortOrderProperty;
|
|
||||||
getStylesheets().add(GroupTreeCell.class.getResource("GroupTreeCell.css").toExternalForm()); //NON-NLS
|
|
||||||
getStyleClass().add("groupTreeCell"); //reduce indent to 5, default is 10 which uses up a lot of space. NON-NLS
|
|
||||||
|
|
||||||
//since end of path is probably more interesting put ellipsis at front
|
|
||||||
setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
prefWidthProperty().bind(getTreeView().widthProperty().subtract(15));
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc }
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected synchronized void updateItem(final GroupTreeNode treeNode, boolean empty) {
|
|
||||||
//if there was a previous group, remove the listeners
|
|
||||||
Optional.ofNullable(getItem())
|
|
||||||
.map(GroupTreeNode::getGroup)
|
|
||||||
.ifPresent(group -> {
|
|
||||||
sortOrder.addListener(fileCountListener);
|
|
||||||
group.getFileIDs().removeListener(fileCountListener);
|
|
||||||
group.hashSetHitsCountProperty().removeListener(fileCountListener);
|
|
||||||
group.seenProperty().removeListener(seenListener);
|
|
||||||
group.uncatCountProperty().removeListener(fileCountListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
super.updateItem(treeNode, empty);
|
|
||||||
|
|
||||||
if (isNull(treeNode) || empty) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setTooltip(null);
|
|
||||||
setText(null);
|
|
||||||
setGraphic(null);
|
|
||||||
setStyle("");
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
DrawableGroup group = treeNode.getGroup();
|
|
||||||
if (isNull(group)) {
|
|
||||||
final String text = getGroupName();
|
|
||||||
//"dummy" group in file system tree <=> a folder with no drawables
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setTooltip(new Tooltip(text));
|
|
||||||
setText(text);
|
|
||||||
setGraphic(new ImageView(EMPTY_FOLDER_ICON));
|
|
||||||
setStyle("");
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//if number of files in this group changes (eg a file is recategorized), update counts via listener
|
|
||||||
group.getFileIDs().addListener(fileCountListener);
|
|
||||||
group.uncatCountProperty().addListener(fileCountListener);
|
|
||||||
group.hashSetHitsCountProperty().addListener(fileCountListener);
|
|
||||||
sortOrder.addListener(fileCountListener);
|
|
||||||
//if the seen state of this group changes update its style
|
|
||||||
group.seenProperty().addListener(seenListener);
|
|
||||||
|
|
||||||
//and use icon corresponding to group type
|
|
||||||
Node icon = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
|
|
||||||
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
|
|
||||||
: group.getGroupKey().getGraphic();
|
|
||||||
final String text = getGroupName() + getCountsText();
|
|
||||||
final String style = getSeenStyleClass();
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setTooltip(new Tooltip(text));
|
|
||||||
setGraphic(icon);
|
|
||||||
setText(text);
|
|
||||||
setStyle(style);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getGroupName() {
|
|
||||||
return Optional.ofNullable(getItem())
|
|
||||||
.map(treeNode -> StringUtils.defaultIfBlank(treeNode.getPath(), DrawableGroup.getBlankGroupName()))
|
|
||||||
.orElse("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return the styleClass to apply based on the assigned group's seen status
|
|
||||||
*
|
|
||||||
* @return the style class to apply
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
private String getSeenStyleClass() {
|
|
||||||
return Optional.ofNullable(getItem())
|
|
||||||
.map(GroupTreeNode::getGroup)
|
|
||||||
.map(DrawableGroup::isSeen)
|
|
||||||
.map(seen -> seen ? "" : "-fx-font-weight:bold;") //NON-NLS
|
|
||||||
.orElse(""); //if item is null or group is null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the counts part of the text to apply to this cell, including
|
|
||||||
* parentheses
|
|
||||||
*
|
|
||||||
* @return get the counts part of the text to apply to this cell
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
private String getCountsText() {
|
|
||||||
return Optional.ofNullable(getItem())
|
|
||||||
.map(GroupTreeNode::getGroup)
|
|
||||||
.map(group ->
|
|
||||||
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
|
|
||||||
? group.getSize()
|
|
||||||
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
|
|
||||||
).orElse(""); //if item is null or group is null
|
|
||||||
}
|
|
||||||
}
|
|
@ -79,8 +79,8 @@ final public class HashHitGroupList extends NavPanel<DrawableGroup> {
|
|||||||
getBorderPane().setCenter(groupList);
|
getBorderPane().setCenter(groupList);
|
||||||
sorted = getController().getGroupManager().getAnalyzedGroups().filtered((DrawableGroup t) -> t.getHashSetHitsCount() > 0).sorted(getDefaultComparator());
|
sorted = getController().getGroupManager().getAnalyzedGroups().filtered((DrawableGroup t) -> t.getHashSetHitsCount() > 0).sorted(getDefaultComparator());
|
||||||
|
|
||||||
groupList.setCellFactory(treeView -> new GroupListCell(getController(), getSortByBox().getSelectionModel().selectedItemProperty()));
|
GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), comparatorProperty());
|
||||||
|
groupList.setCellFactory(groupCellFactory::getListCell);
|
||||||
groupList.setItems(sorted);
|
groupList.setItems(sorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,63 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import java.lang.String?>
|
|
||||||
<?import javafx.collections.FXCollections?>
|
|
||||||
<?import javafx.scene.control.ComboBox?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.control.Tab?>
|
<?import javafx.scene.control.Tab?>
|
||||||
<?import javafx.scene.control.RadioButton?>
|
|
||||||
<?import javafx.scene.control.ToggleGroup?>
|
|
||||||
<?import javafx.scene.control.ToolBar?>
|
<?import javafx.scene.control.ToolBar?>
|
||||||
<?import javafx.scene.image.Image?>
|
|
||||||
<?import javafx.scene.image.ImageView?>
|
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
|
||||||
|
|
||||||
|
<fx:root closable="false" type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<fx:root type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" closable="false" >
|
|
||||||
<content>
|
<content>
|
||||||
<BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="borderPane">
|
<BorderPane fx:id="borderPane">
|
||||||
<top>
|
<top>
|
||||||
<ToolBar fx:id="toolBar" BorderPane.alignment="CENTER">
|
<ToolBar fx:id="toolBar" minWidth="-Infinity" BorderPane.alignment="CENTER" />
|
||||||
<items>
|
|
||||||
<Label fx:id="sortByBoxLabel" text="Sort By:" />
|
|
||||||
<ComboBox fx:id="sortByBox" maxWidth="-Infinity" minWidth="-Infinity">
|
|
||||||
<items>
|
|
||||||
<FXCollections fx:factory="observableArrayList">
|
|
||||||
<String fx:value="Item 1" />
|
|
||||||
<String fx:value="Item 2" />
|
|
||||||
<String fx:value="Item 3" />
|
|
||||||
</FXCollections>
|
|
||||||
</items>
|
|
||||||
</ComboBox>
|
|
||||||
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0">
|
|
||||||
<children>
|
|
||||||
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending">
|
|
||||||
<graphic>
|
|
||||||
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../images/arrow_up.png" />
|
|
||||||
</image>
|
|
||||||
</ImageView>
|
|
||||||
</graphic>
|
|
||||||
<toggleGroup>
|
|
||||||
<ToggleGroup fx:id="orderGroup" />
|
|
||||||
</toggleGroup>
|
|
||||||
</RadioButton>
|
|
||||||
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup">
|
|
||||||
<graphic>
|
|
||||||
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../images/arrow_down.png" />
|
|
||||||
</image>
|
|
||||||
</ImageView>
|
|
||||||
</graphic>
|
|
||||||
</RadioButton>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
</items>
|
|
||||||
</ToolBar>
|
|
||||||
</top>
|
</top>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
</content>
|
</content>
|
||||||
|
@ -22,15 +22,13 @@ import com.google.common.eventbus.Subscribe;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.ComboBox;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.RadioButton;
|
|
||||||
import javafx.scene.control.SelectionModel;
|
import javafx.scene.control.SelectionModel;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.ToggleGroup;
|
|
||||||
import javafx.scene.control.ToolBar;
|
import javafx.scene.control.ToolBar;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javax.swing.SortOrder;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
@ -38,6 +36,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
|||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
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.
|
* Base class for Tabs in the left hand Navigation/Context area.
|
||||||
@ -50,24 +49,10 @@ abstract class NavPanel<X> extends Tab {
|
|||||||
@FXML
|
@FXML
|
||||||
private ToolBar toolBar;
|
private ToolBar toolBar;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ComboBox<GroupComparators<?>> sortByBox;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private RadioButton ascRadio;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ToggleGroup orderGroup;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private RadioButton descRadio;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label sortByBoxLabel;
|
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
private final GroupManager groupManager;
|
private final GroupManager groupManager;
|
||||||
private final CategoryManager categoryManager;
|
private final CategoryManager categoryManager;
|
||||||
|
private SortChooser<DrawableGroup, GroupComparators<?>> sortChooser;
|
||||||
|
|
||||||
NavPanel(ImageGalleryController controller) {
|
NavPanel(ImageGalleryController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
@ -75,34 +60,35 @@ abstract class NavPanel<X> extends Tab {
|
|||||||
this.categoryManager = controller.getCategoryManager();
|
this.categoryManager = controller.getCategoryManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<GroupComparators<?>> comparatorProperty() {
|
||||||
|
return sortChooser.comparatorProperty();
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@NbBundle.Messages({"NavPanel.ascRadio.text=Ascending",
|
@NbBundle.Messages({"NavPanel.ascRadio.text=Ascending",
|
||||||
"NavPanel.descRadio.text=Descending",
|
"NavPanel.descRadio.text=Descending",
|
||||||
"NavPanel.sortByBoxLabel.text=Sort By:"})
|
"NavPanel.sortByBoxLabel.text=Sort By:"})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert borderPane != null : "fx:id=\"borderPane\" was not injected: check your FXML file 'NavPanel.fxml'.";
|
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'.";
|
assert toolBar != null : "fx:id=\"toolBar\" was not injected: check your FXML file 'NavPanel.fxml'.";
|
||||||
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'.";
|
|
||||||
assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'NavPanel.fxml'.";
|
|
||||||
assert orderGroup != null : "fx:id=\"orderGroup\" was not injected: check your FXML file 'NavPanel.fxml'.";
|
|
||||||
assert descRadio != null : "fx:id=\"descRadio\" was not injected: check your FXML file 'NavPanel.fxml'.";
|
|
||||||
|
|
||||||
sortByBox.getItems().setAll(GroupComparators.getValues());
|
sortChooser = new SortChooser<>(GroupComparators.getValues());
|
||||||
sortByBox.getSelectionModel().select(getDefaultComparator());
|
sortChooser.setComparator(getDefaultComparator());
|
||||||
orderGroup.selectedToggleProperty().addListener(order -> sortGroups());
|
sortChooser.sortOrderProperty().addListener(order -> sortGroups());
|
||||||
sortByBox.getSelectionModel().selectedItemProperty().addListener(observable -> {
|
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
||||||
sortGroups();
|
sortGroups();
|
||||||
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count
|
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count
|
||||||
if (sortByBox.getSelectionModel().getSelectedItem() == GroupComparators.UNCATEGORIZED_COUNT) {
|
if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) {
|
||||||
categoryManager.registerListener(NavPanel.this);
|
categoryManager.registerListener(NavPanel.this);
|
||||||
} else {
|
} else {
|
||||||
categoryManager.unregisterListener(NavPanel.this);
|
categoryManager.unregisterListener(NavPanel.this);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
ascRadio.setText(Bundle.NavPanel_ascRadio_text());
|
final SortChooser.ValueType valueType = newComparator == GroupComparators.ALPHABETICAL ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
|
||||||
descRadio.setText(Bundle.NavPanel_descRadio_text());
|
sortChooser.setValueType(valueType);
|
||||||
sortByBoxLabel.setText(Bundle.NavPanel_sortByBoxLabel_text());
|
});
|
||||||
|
toolBar.getItems().add(sortChooser);
|
||||||
|
|
||||||
//keep selection in sync with controller
|
//keep selection in sync with controller
|
||||||
controller.viewState().addListener(observable -> {
|
controller.viewState().addListener(observable -> {
|
||||||
Optional.ofNullable(controller.viewState().get())
|
Optional.ofNullable(controller.viewState().get())
|
||||||
@ -129,8 +115,8 @@ abstract class NavPanel<X> extends Tab {
|
|||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
Comparator<DrawableGroup> getComparator() {
|
Comparator<DrawableGroup> getComparator() {
|
||||||
Comparator<DrawableGroup> comparator = sortByBox.getSelectionModel().getSelectedItem();
|
Comparator<DrawableGroup> comparator = sortChooser.getComparator();
|
||||||
return (orderGroup.getSelectedToggle() == ascRadio)
|
return (sortChooser.getSortOrder() == SortOrder.ASCENDING)
|
||||||
? comparator
|
? comparator
|
||||||
: comparator.reversed();
|
: comparator.reversed();
|
||||||
}
|
}
|
||||||
@ -194,22 +180,6 @@ abstract class NavPanel<X> extends Tab {
|
|||||||
return toolBar;
|
return toolBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox<GroupComparators<?>> getSortByBox() {
|
|
||||||
return sortByBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
RadioButton getAscRadio() {
|
|
||||||
return ascRadio;
|
|
||||||
}
|
|
||||||
|
|
||||||
ToggleGroup getOrderGroup() {
|
|
||||||
return orderGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
RadioButton getDescRadio() {
|
|
||||||
return descRadio;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageGalleryController getController() {
|
ImageGalleryController getController() {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 699 B |
Binary file not shown.
After Width: | Height: | Size: 768 B |
Binary file not shown.
After Width: | Height: | Size: 714 B |
Binary file not shown.
After Width: | Height: | Size: 707 B |
Loading…
x
Reference in New Issue
Block a user