From 727670c5dc4d10aa61ac95fe2dffd9b4b73cc6bd Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 10 Feb 2016 15:14:03 -0500 Subject: [PATCH 01/67] grab files form new datasources even if they are not images. Extract BulkTask out of PrePopulateDataSourceFiles and CopyAnalyzedFiles --- .../imagegallery/ImageGalleryController.java | 322 +++++++++--------- 1 file changed, 167 insertions(+), 155 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index c3aedf6dcc..2290671c19 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.imagegallery; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -85,6 +86,7 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.VirtualDirectory; /** * Connects different parts of ImageGallery together and is hub for flow of @@ -290,16 +292,16 @@ public final class ImageGalleryController implements Executor { * aren't, add a blocking progress spinner with appropriate message. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @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.", - "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.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.", - "ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.", - "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" + - " the current Group By setting resulted in no groups, " + - "or no groups are fully analyzed but ingest is not running."}) + @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.", + "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.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.", + "ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.", + "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" + + " the current Group By setting resulted in no groups, " + + "or no groups are fully analyzed but ingest is not running."}) public void checkForGroups() { if (groupManager.getAnalyzedGroups().isEmpty()) { if (IngestManager.getInstance().isIngestRunning()) { @@ -567,7 +569,7 @@ public final class ImageGalleryController implements Executor { * Abstract base class for task to be done on {@link DBWorkerThread} */ @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 { public double getProgress() { @@ -699,30 +701,18 @@ public final class ImageGalleryController implements Executor { } } + } - /** - * 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.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 { + @NbBundle.Messages({"BulkTask.committingDb.status=commiting image/video database", + "BulkTask.stopCopy.status=Stopping copy to drawable db task.", + "BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."}) + abstract static private class BulkTask extends InnerTask { - private final ImageGalleryController controller; - private final DrawableDB taskDB; - private final SleuthkitCase tskCase; - - CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { - this.controller = controller; - this.taskDB = taskDB; - this.tskCase = tskCase; - } + protected final ImageGalleryController controller; + protected final DrawableDB taskDB; + protected final SleuthkitCase tskCase; + ProgressHandle progressHandle; static private final String FILE_EXTENSION_CLAUSE = "(name LIKE '%." //NON-NLS @@ -734,30 +724,42 @@ public final class ImageGalleryController implements Executor { + StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(), "' OR mime_type LIKE '") //NON-NLS + "') "; - static private final String DRAWABLE_QUERY = + static final String DRAWABLE_QUERY = //grab files with supported extension - FILE_EXTENSION_CLAUSE + "(" + 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()); + + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS + + BulkTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + this.controller = controller; + this.taskDB = taskDB; + this.tskCase = tskCase; + } + + abstract void cleanup(boolean success); + + abstract List getFiles() throws TskCoreException; + + abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException; @Override public void run() { + progressHandle = getInitialProgressHandle(); progressHandle.start(); updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status()); try { //grab all files with supported extension or detected mime types - final List files = tskCase.findAllFilesWhere(DRAWABLE_QUERY); + final List files = getFiles(); progressHandle.switchToDeterminate(files.size()); updateProgress(0.0); //do in transaction DrawableDB.DrawableTransaction tr = taskDB.beginTransaction(); - int units = 0; + int workDone = 0; for (final AbstractFile f : files) { if (isCancelled()) { LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS @@ -765,161 +767,172 @@ public final class ImageGalleryController implements Executor { break; } - final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; + processFile(f, tr); - if (known) { - taskDB.removeFile(f.getId(), tr); //remove known files - } else { - Optional 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()); + workDone++; + progressHandle.progress(f.getName(), workDone); + updateProgress(workDone - 1 / (double) files.size()); updateMessage(f.getName()); } progressHandle.finish(); - - progressHandle = ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_committingDb_status()); - updateMessage(Bundle.CopyAnalyzedFiles_committingDb_status()); + progressHandle = ProgressHandleFactory.createHandle(Bundle.BulkTask_committingDb_status()); + updateMessage(Bundle.BulkTask_committingDb_status()); updateProgress(1.0); progressHandle.start(); taskDB.commitTransaction(tr, true); } catch (TskCoreException ex) { - progressHandle.progress(Bundle.CopyAnalyzedFiles_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 - MessageNotifyUtil.Notify.warn(Bundle.CopyAnalyzedFiles_errPopulating_errMsg(), ex.getMessage()); + progressHandle.progress(Bundle.BulkTask_stopCopy_status()); + LOGGER.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS + MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage()); + cleanup(false); + return; + } finally { progressHandle.finish(); updateMessage(""); updateProgress(-1.0); - controller.setStale(true); - return; } + cleanup(true); + } - progressHandle.finish(); - updateMessage(""); - updateProgress(-1.0); - controller.setStale(false); + abstract ProgressHandle getInitialProgressHandle(); + + } + + /** + * 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'. + */ + static private class CopyAnalyzedFiles extends BulkTask { + + private static final Logger LOGGER = Logger.getLogger(CopyAnalyzedFiles.class.getName()); + + CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + super(controller, taskDB, tskCase); + } + + @Override + protected void cleanup(boolean success) { + controller.setStale(!success); + } + + @Override + List 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 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 - * fs_obj_id to identify files from new datasources) + * Copy files from a newly added data source into the DB. Get all "drawable" + * 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 * netbeans and ImageGallery progress/status */ - @NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database", - "PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"}) - private class PrePopulateDataSourceFiles extends InnerTask { + static private class PrePopulateDataSourceFiles extends BulkTask { + + private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName()); 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 */ - PrePopulateDataSourceFiles(Content dataSource) { - super(); + PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + super(controller, taskDB, tskCase); 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 - public void run() { - progressHandle.start(); - updateMessage(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status()); + protected void cleanup(boolean success) { - try { - String fsQuery = "(fs_obj_id IS NULL) "; //default clause NON-NLS + } + + @Override + void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) { + taskDB.insertFile(DrawableFile.create(f, false, false), tr); + } + + @Override + List getFiles() throws TskCoreException { + if (dataSource instanceof Image) { + List 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 in DB. for them, we will not specify a fs_obj_id, - * which means we will grab files from another data source, but - * the drawable DB is smart enough to de-dupe them. For Images - * we can do better. + * fs_obj_id is set only for file system files, so we will match + * the VirtualDirectory's name in the parent path. + * + * TODO: A future database schema could probably make this + * cleaner. If we had a datasource_id column in the files table + * we could just match agains that. */ - if (dataSource instanceof Image) { - List 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; - } - //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 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 + return tskCase.findAllFilesWhere(" parent_path LIKE '/" + dataSource.getName() + "/%' AND " + DRAWABLE_QUERY); //NON-NLS + } else { + String msg = "Uknown datasource type: " + dataSource.getClass().getName(); + LOGGER.log(Level.SEVERE, msg); + throw new IllegalArgumentException(msg); } + } - progressHandle.finish(); + @Override + @NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",}) + ProgressHandle getInitialProgressHandle() { + return ProgressHandleFactory.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this); } } @@ -1011,7 +1024,7 @@ public final class ImageGalleryController implements Executor { //copy all file data to drawable databse Content newDataSource = (Content) evt.getNewValue(); if (isListeningEnabled()) { - queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource)); + queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase())); } else {//TODO: keep track of what we missed for later setStale(true); } @@ -1028,7 +1041,6 @@ public final class ImageGalleryController implements Executor { getTagsManager().fireTagDeletedEvent(tagDeletedEvent); } break; - } } } From 9075f860a5dee1d75aee7fa71b6065c1987e1b13 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 10 Feb 2016 15:22:02 -0500 Subject: [PATCH 02/67] cleanup --- .../imagegallery/ImageGalleryController.java | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 2290671c19..7fb7f9032f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -55,7 +55,6 @@ import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javax.annotation.Nullable; import javax.swing.SwingUtilities; -import org.apache.commons.lang3.StringUtils; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.util.Cancellable; @@ -587,9 +586,9 @@ public final class ImageGalleryController implements Executor { public final void updateMessage(String Status) { this.message.set(Status); } - SimpleObjectProperty 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()); + private final SimpleObjectProperty 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()); public SimpleDoubleProperty progressProperty() { return progress; @@ -646,7 +645,6 @@ public final class ImageGalleryController implements Executor { this.file = f; this.taskDB = taskDB; } - } /** @@ -701,38 +699,38 @@ public final class ImageGalleryController implements Executor { } } - } @NbBundle.Messages({"BulkTask.committingDb.status=commiting image/video database", "BulkTask.stopCopy.status=Stopping copy to drawable db task.", "BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."}) - abstract static private class BulkTask extends InnerTask { - - protected final ImageGalleryController controller; - protected final DrawableDB taskDB; - protected final SleuthkitCase tskCase; - ProgressHandle progressHandle; + abstract static private class BulkTransferTask extends InnerTask { static private final String FILE_EXTENSION_CLAUSE = "(name LIKE '%." //NON-NLS - + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") //NON-NLS + + String.join("' OR name LIKE '%.", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS + "')"; static private final String MIMETYPE_CLAUSE = "(mime_type LIKE '" //NON-NLS - + StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(), "' OR 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 + "(" + 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 - BulkTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + final ImageGalleryController controller; + final DrawableDB taskDB; + final SleuthkitCase tskCase; + + ProgressHandle progressHandle; + + BulkTransferTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { this.controller = controller; this.taskDB = taskDB; this.tskCase = tskCase; @@ -798,7 +796,6 @@ public final class ImageGalleryController implements Executor { } abstract ProgressHandle getInitialProgressHandle(); - } /** @@ -808,7 +805,7 @@ public final class ImageGalleryController implements Executor { * adds them to the Drawable DB. Uses the presence of a mimetype as an * approximation to 'analyzed'. */ - static private class CopyAnalyzedFiles extends BulkTask { + static private class CopyAnalyzedFiles extends BulkTransferTask { private static final Logger LOGGER = Logger.getLogger(CopyAnalyzedFiles.class.getName()); @@ -869,7 +866,7 @@ public final class ImageGalleryController implements Executor { * TODO: create methods to simplify progress value/text updates to both * netbeans and ImageGallery progress/status */ - static private class PrePopulateDataSourceFiles extends BulkTask { + static private class PrePopulateDataSourceFiles extends BulkTransferTask { private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName()); @@ -886,7 +883,6 @@ public final class ImageGalleryController implements Executor { @Override protected void cleanup(boolean success) { - } @Override From 4fb8635fb4b811145e92911b18de72897eb62a9c Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 4 Feb 2016 17:01:51 -0500 Subject: [PATCH 03/67] refactored GroupListCell and GroupTreeCell into GroupCellFactory to reduce code duplciation. cleanup GroupCellFactory and related --- .../datamodel/grouping/GroupManager.java | 14 +- .../{GroupTreeCell.css => GroupCell.css} | 2 +- .../gui/navpanel/GroupCellFactory.java | 306 ++++++++++++++++++ .../gui/navpanel/GroupListCell.java | 174 ---------- .../imagegallery/gui/navpanel/GroupTree.java | 3 +- .../gui/navpanel/GroupTreeCell.java | 193 ----------- .../gui/navpanel/HashHitGroupList.java | 5 +- 7 files changed, 318 insertions(+), 379 deletions(-) rename ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/{GroupTreeCell.css => GroupCell.css} (87%) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java delete mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupListCell.java delete mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index cebcd6c222..e1f497a295 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -666,7 +666,7 @@ public class GroupManager { group = new DrawableGroup(groupKey, fileIDs, groupSeen); controller.getCategoryManager().registerListener(group); group.seenProperty().addListener((o, oldSeen, newSeen) -> { - markGroupSeen(group, newSeen); + Platform.runLater(() -> markGroupSeen(group, newSeen)); }); groupMap.put(groupKey, group); } @@ -719,12 +719,12 @@ public class GroupManager { */ @SuppressWarnings({"unchecked", "rawtypes"}) @NbBundle.Messages({"# {0} - groupBy attribute Name", - "# {1} - sortBy name", - "# {2} - sort Order", - "ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order", - "# {0} - groupBy attribute Name", - "# {1} - atribute value", - "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) + "# {1} - sortBy name", + "# {2} - sort Order", + "ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order", + "# {0} - groupBy attribute Name", + "# {1} - atribute value", + "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) private class ReGroupTask> extends LoggedTask { private ProgressHandle groupProgress; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.css b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCell.css similarity index 87% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.css rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCell.css index 2302510cc7..6d7c52b302 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.css +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCell.css @@ -1,4 +1,4 @@ -.groupTreeCell{ +.groupCell{ -fx-indent:5; /* default indent is 10 */ } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java new file mode 100644 index 0000000000..d21b76d018 --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java @@ -0,0 +1,306 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 Basis Technology Corp. + * Contact: carrier sleuthkit 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 or + * TreeView + */ +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> sortOrder; + private final ImageGalleryController controller; + + GroupCellFactory(ImageGalleryController controller, ReadOnlyObjectProperty> sortOrderProperty) { + this.controller = controller; + this.sortOrder = sortOrderProperty; + } + + GroupListCell getListCell(ListView 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 & 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 & 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 > 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 implements GroupCell> { + + 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 getView() { + return getTreeView(); + } + + @Override + public String getGroupName() { + return Optional.ofNullable(getItem()) + .map(treeNode -> StringUtils.defaultIfBlank(treeNode.getPath(), DrawableGroup.getBlankGroupName())) + .orElse(""); + } + + @Override + public Optional 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 implements GroupCell> { + + 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 getView() { + return getListView(); + } + + @Override + public String getGroupName() { + return Optional.ofNullable(getItem()) + .map(group -> group.getGroupByValueDislpayName()) + .orElse(""); + } + + @Override + public Optional 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 { + + String getGroupName(); + + X getView(); + + Optional getGroup(); + + InvalidationListener getGroupListener(); + } + + private class GroupListener> 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); + }); + } + } +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupListCell.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupListCell.java deleted file mode 100644 index 6a48dc78b4..0000000000 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupListCell.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2015-16 Basis Technology Corp. - * Contact: carrier sleuthkit 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 { - - /** - * 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> sortOrder; - private final ImageGalleryController controller; - - GroupListCell(ImageGalleryController controller, ReadOnlyObjectProperty> 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 - } -} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index 0813c91190..5062f1ba37 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -71,8 +71,9 @@ final public class GroupTree extends NavPanel> { BooleanBinding groupedByPath = Bindings.equal(getGroupManager().getGroupByProperty(), DrawableAttribute.PATH); getToolBar().visibleProperty().bind(groupedByPath.not()); getToolBar().managedProperty().bind(groupedByPath.not()); + GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), getSortByBox().getSelectionModel().selectedItemProperty()); - groupTree.setCellFactory(treeView -> new GroupTreeCell(getController(), getSortByBox().getSelectionModel().selectedItemProperty())); + groupTree.setCellFactory(groupCellFactory::getTreeCell); groupTree.setShowRoot(false); getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change change) -> { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java deleted file mode 100644 index e81633ad16..0000000000 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-16 Basis Technology Corp. - * Contact: carrier sleuthkit 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 { - - /** - * 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> sortOrder; - private final ImageGalleryController controller; - - GroupTreeCell(ImageGalleryController controller, ReadOnlyObjectProperty> 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 - } -} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java index 790262b78e..a7fd97723b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java @@ -78,9 +78,8 @@ final public class HashHitGroupList extends NavPanel { getBorderPane().setCenter(groupList); 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(), getSortByBox().getSelectionModel().selectedItemProperty()); + groupList.setCellFactory(groupCellFactory::getListCell); groupList.setItems(sorted); } From 415840815dda40af38775882565c1e0cdfb463ae Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 8 Feb 2016 16:51:01 -0500 Subject: [PATCH 04/67] add Windows meta files (WMF/EMF) to list of ImageTypes, read support requires library(Batik) --- .../src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java index 72b1eba094..806704312f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java @@ -107,6 +107,8 @@ public enum FileTypeUtils { , "sn", "ras" //sun raster NON-NLS , "ico" //windows icons 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 @@ -129,6 +131,8 @@ public enum FileTypeUtils { * mime types. */ 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 supportedMimeTypes.addAll(Stream.of(ImageIO.getReaderMIMETypes()) From 133471b07339d7e89635fbcf610f4261a20c30b2 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Feb 2016 15:38:06 -0500 Subject: [PATCH 05/67] add category color icons to the right click menus in IG --- .../imagegallery/actions/AddTagAction.java | 14 +- .../actions/CategorizeAction.java | 119 ++++---- .../actions/CategorizeGroupAction.java | 12 +- .../CategorizeSelectedFilesAction.java | 6 +- .../imagegallery/datamodel/Category.java | 20 +- .../gui/drawableviews/DrawableTileBase.java | 6 +- .../gui/drawableviews/GroupPane.java | 253 +++++++++--------- 7 files changed, 212 insertions(+), 218 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java index 7742ec583c..b633c08ced 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java @@ -25,7 +25,6 @@ import javafx.event.ActionEvent; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javax.swing.SwingUtilities; - import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; @@ -33,7 +32,6 @@ import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog; import org.sleuthkit.autopsy.actions.GetTagNameDialog; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.datamodel.TagName; /** @@ -74,9 +72,9 @@ abstract class AddTagAction { // @@@ This user interface has some significant usability issues and needs // to be reworked. @NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag", - "AddTagAction.menuItem.noTags=No tags", - "AddTagAction.menuItem.newTag=New Tag...", - "AddTagAction.menuItem.tagAndComment=Tag and Comment..."}) + "AddTagAction.menuItem.noTags=No tags", + "AddTagAction.menuItem.newTag=New Tag...", + "AddTagAction.menuItem.tagAndComment=Tag and Comment..."}) protected class TagMenu extends Menu { TagMenu(ImageGalleryController controller) { @@ -133,11 +131,7 @@ abstract class AddTagAction { SwingUtilities.invokeLater(() -> { GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow()); if (null != tagNameAndComment) { - if (CategoryManager.isCategoryTagName(tagNameAndComment.getTagName())) { - new CategorizeAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); - } else { - new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); - } + new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); } }); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 8c819373c8..ab59df29e4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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 java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.event.ActionEvent; +import javafx.collections.ObservableSet; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; +import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import javax.swing.JOptionPane; - +import org.controlsfx.control.action.Action; +import org.controlsfx.control.action.ActionUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; 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; @@ -49,48 +51,42 @@ import org.sleuthkit.datamodel.TagName; 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"}) -public class CategorizeAction extends AddTagAction { +public class CategorizeAction extends Action { private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName()); private final ImageGalleryController controller; private final UndoRedoManager undoManager; + private final Category cat; + private final Set selectedFileIDs; + private final Boolean createUndo; - public CategorizeAction(ImageGalleryController controller) { - super(); - this.controller = controller; - undoManager = controller.getUndoManager(); + public CategorizeAction(ImageGalleryController controller, Category cat, Set selectedFileIDs) { + this(controller, cat, selectedFileIDs, true); } - public Menu getPopupMenu() { + private CategorizeAction(ImageGalleryController controller, Category cat, Set 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); } - @Override - protected String getActionDisplayName() { - return Bundle.CategorizeAction_displayName(); - } - - @Override - public void addTag(TagName tagName, String comment) { - Set selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected()); - addTagsToFiles(tagName, comment, selectedFiles); - } - - @Override - protected void addTagsToFiles(TagName tagName, String comment, Set selectedFiles) { - addTagsToFiles(tagName, comment, selectedFiles, true); - } - - public void addTagsToFiles(TagName tagName, String comment, Set 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)); + private void addCatToFiles() { + Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFileIDs.toString(), cat.getDisplayName()}); //NON-NLS + controller.queueDBWorkerTask(new CategorizeTask(selectedFileIDs, cat, createUndo)); } /** @@ -101,49 +97,43 @@ public class CategorizeAction extends AddTagAction { CategoryMenu(ImageGalleryController controller) { super(Bundle.CategorizeAction_displayName()); + setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon())); + ObservableSet selected = controller.getSelectionModel().getSelected(); // Each category get an item in the sub-menu. Selecting one of these menu items adds // a tag with the associated category. for (final Category cat : Category.values()) { - - 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())))); + MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected)); getItems().add(categoryItem); } } } @NbBundle.Messages({"# {0} - fileID number", - "CategorizeTask.errorUnable.msg=Unable to categorize {0}.", - "CategorizeTask.errorUnable.title=Categorizing Error"}) + "CategorizeTask.errorUnable.msg=Unable to categorize {0}.", + "CategorizeTask.errorUnable.title=Categorizing Error"}) private class CategorizeTask extends ImageGalleryController.InnerTask { private final Set fileIDs; - @Nonnull - private final TagName tagName; - private final String comment; - private final boolean createUndo; - CategorizeTask(Set fileIDs, @Nonnull TagName tagName, String comment, boolean createUndo) { + private final boolean createUndo; + private final Category cat; + + CategorizeTask(Set fileIDs, @Nonnull Category cat, boolean createUndo) { super(); this.fileIDs = fileIDs; - java.util.Objects.requireNonNull(tagName); - this.tagName = tagName; - this.comment = comment; + java.util.Objects.requireNonNull(cat); + this.cat = cat; this.createUndo = createUndo; - } - @Override public void run() { final DrawableTagsManager tagsManager = controller.getTagsManager(); final CategoryManager categoryManager = controller.getCategoryManager(); - Map oldCats = new HashMap<>(); + Map oldCats = new HashMap<>(); + TagName tagName = categoryManager.getTagName(cat); + TagName catZeroTagName = categoryManager.getTagName(Category.ZERO); for (long fileID : fileIDs) { try { DrawableFile file = controller.getFileFromId(fileID); //drawable db access @@ -151,12 +141,12 @@ public class CategorizeAction extends AddTagAction { Category oldCat = file.getCategory(); //drawable db access TagName oldCatTagName = categoryManager.getTagName(oldCat); if (false == tagName.equals(oldCatTagName)) { - oldCats.put(fileID, oldCatTagName); + oldCats.put(fileID, oldCat); } } final List fileTags = tagsManager.getContentTagsByContent(file); - if (tagName == categoryManager.getTagName(Category.ZERO)) { + if (tagName.equals(catZeroTagName)) { // delete all cat tags for cat-0 fileTags.stream() .filter(tag -> CategoryManager.isCategoryTagName(tag.getName())) @@ -173,7 +163,7 @@ public class CategorizeAction extends AddTagAction { .map(Tag::getName) .filter(tagName::equals) .collect(Collectors.toList()).isEmpty()) { - tagsManager.addContentTag(file, tagName, comment); + tagsManager.addContentTag(file, tagName, ""); } } } catch (TskCoreException ex) { @@ -186,7 +176,7 @@ public class CategorizeAction extends AddTagAction { } 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 private final class CategorizationChange implements UndoRedoManager.UndoableCommand { - private final TagName newCategory; - private final ImmutableMap oldCategories; + private final Category newCategory; + private final ImmutableMap oldCategories; private final ImageGalleryController controller; - CategorizationChange(ImageGalleryController controller, TagName newCategory, Map oldCategories) { + CategorizationChange(ImageGalleryController controller, Category newCategory, Map oldCategories) { this.controller = controller; this.newCategory = newCategory; this.oldCategories = ImmutableMap.copyOf(oldCategories); @@ -213,8 +203,8 @@ public class CategorizeAction extends AddTagAction { */ @Override public void run() { - CategorizeAction categorizeAction = new CategorizeAction(controller); - categorizeAction.addTagsToFiles(newCategory, "", this.oldCategories.keySet(), false); + CategorizeAction categorizeAction = new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false); + categorizeAction.addCatToFiles(); } /** @@ -223,9 +213,10 @@ public class CategorizeAction extends AddTagAction { */ @Override public void undo() { - CategorizeAction categorizeAction = new CategorizeAction(controller); - for (Map.Entry entry : oldCategories.entrySet()) { - categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false); + + for (Map.Entry entry : oldCategories.entrySet()) { + new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false) + .addCatToFiles(); } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index b060b645c9..00df7e1fc0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -18,8 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; -import com.google.common.collect.ImmutableSet; -import java.util.Set; +import java.util.HashSet; import org.controlsfx.control.action.Action; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; @@ -30,10 +29,9 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.Category; public class CategorizeGroupAction extends Action { public CategorizeGroupAction(Category cat, ImageGalleryController controller) { - super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> { - Set fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs()); - new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet); - }); - setGraphic(cat.getGraphic()); + super(cat.getDisplayName(), actionEvent -> + new CategorizeAction(controller, cat, new HashSet<>(controller.viewState().get().getGroup().getFileIDs())) + .handle(actionEvent) + ); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java index 04a2a93e9e..7290ffb6ca 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java @@ -18,17 +18,15 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; -import org.controlsfx.control.action.Action; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; /** * */ -public class CategorizeSelectedFilesAction extends Action { +public class CategorizeSelectedFilesAction extends CategorizeAction { public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) { - super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), "")); - setGraphic(cat.getGraphic()); + super(controller, cat, controller.getSelectionModel().getSelected()); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java index 36d84f90e7..1b2bd604b0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java @@ -25,6 +25,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javafx.geometry.Insets; 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.BackgroundFill; import javafx.scene.layout.Border; @@ -94,6 +97,7 @@ public enum Category { private final String displayName; private final int id; + private Image snapshot; private Category(Color color, int id, String name) { this.color = color; @@ -118,11 +122,15 @@ public enum Category { return displayName; } - public Node getGraphic() { - Region region = new Region(); - region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY))); - region.setPrefSize(16, 16); - region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2))); - return region; + synchronized public Node getGraphic() { + if (snapshot == null) { + Region region = new Region(); + region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY))); + region.setPrefSize(16, 16); + 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); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index b25b76f7ff..a353d97cc0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -142,7 +142,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { * @param controller the value of controller */ @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) { super(controller); this.groupPane = groupPane; @@ -182,11 +182,10 @@ public abstract class DrawableTileBase extends DrawableUIBase { private ContextMenu buildContextMenu(DrawableFile file) { final ArrayList menuItems = new ArrayList<>(); - menuItems.add(new CategorizeAction(getController()).getPopupMenu()); + menuItems.add(CategorizeAction.getCategoriesMenu(getController())); menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu()); - final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles()); extractMenuItem.setOnAction(actionEvent -> { SwingUtilities.invokeLater(() -> { @@ -196,7 +195,6 @@ public abstract class DrawableTileBase extends DrawableUIBase { }); menuItems.add(extractMenuItem); - MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer()); contentViewer.setOnAction(actionEvent -> { SwingUtilities.invokeLater(() -> { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 160b6ccbcc..6cb63fb84c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Arrays; @@ -47,6 +48,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; @@ -143,39 +145,39 @@ import org.sleuthkit.datamodel.TskCoreException; * https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview */ public class GroupPane extends BorderPane { - + private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(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 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)) ); - + private final FileIDSelectionModel selectionModel; private static final List 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); - + private final Back backAction; - + private final Forward forwardAction; - + @FXML private Button undoButton; @FXML private Button redoButton; - + @FXML private SplitMenuButton catSelectedSplitMenu; - + @FXML private SplitMenuButton tagSelectedSplitMenu; - + @FXML private ToolBar headerToolBar; - + @FXML private ToggleButton cat0Toggle; @FXML @@ -188,30 +190,30 @@ public class GroupPane extends BorderPane { private ToggleButton cat4Toggle; @FXML private ToggleButton cat5Toggle; - + @FXML private SegmentedButton segButton; - + private SlideShowView slideShowPane; - + @FXML private ToggleButton slideShowToggle; - + @FXML private GridView gridView; - + @FXML private ToggleButton tileToggle; - + @FXML private Button nextButton; - + @FXML private Button backButton; - + @FXML private Button forwardButton; - + @FXML private Label groupLabel; @FXML @@ -222,24 +224,24 @@ public class GroupPane extends BorderPane { private Label catContainerLabel; @FXML private Label catHeadingLabel; - + @FXML private HBox catSegmentedContainer; @FXML private HBox catSplitMenuContainer; - + private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); - + private final NextUnseenGroup nextGroupAction; - + private final ImageGalleryController controller; - + private ContextMenu contextMenu; - + private Integer selectionAnchorIndex; private final UndoAction undoAction; private final RedoAction redoAction; - + GroupViewMode getGroupViewMode() { return groupViewMode.get(); } @@ -262,7 +264,7 @@ public class GroupPane extends BorderPane { */ @ThreadConfined(type = ThreadType.JFX) private final Map cellMap = new HashMap<>(); - + private final InvalidationListener filesSyncListener = (observable) -> { final String header = getHeaderString(); final List fileIds = getGroup().getFileIDs(); @@ -272,7 +274,7 @@ public class GroupPane extends BorderPane { groupLabel.setText(header); }); }; - + public GroupPane(ImageGalleryController controller) { this.controller = controller; this.selectionModel = controller.getSelectionModel(); @@ -281,10 +283,10 @@ public class GroupPane extends BorderPane { forwardAction = new Forward(controller); undoAction = new UndoAction(controller); redoAction = new RedoAction(controller); - + FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS } - + @ThreadConfined(type = ThreadType.JFX) public void activateSlideShowViewer(Long slideShowFileID) { groupViewMode.set(GroupViewMode.SLIDE_SHOW); @@ -300,16 +302,16 @@ public class GroupPane extends BorderPane { } else { slideShowPane.setFile(slideShowFileID); } - + setCenter(slideShowPane); slideShowPane.requestFocus(); - + } - + void syncCatToggle(DrawableFile file) { getToggleForCategory(file.getCategory()).setSelected(true); } - + public void activateTileViewer() { groupViewMode.set(GroupViewMode.TILE); tileToggle.setSelected(true); @@ -321,11 +323,11 @@ public class GroupPane extends BorderPane { slideShowPane = null; this.scrollToFileID(selectionModel.lastSelectedProperty().get()); } - + public DrawableGroup getGroup() { return grouping.get(); } - + private void selectAllFiles() { selectionModel.clearAndSelectAll(getGroup().getFileIDs()); } @@ -342,15 +344,15 @@ public class GroupPane extends BorderPane { : Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()), getGroup().getHashSetHitsCount(), getGroup().getSize()); } - + ContextMenu getContextMenu() { return contextMenu; } - + ReadOnlyObjectProperty grouping() { return grouping.getReadOnlyProperty(); } - + private ToggleButton getToggleForCategory(Category category) { switch (category) { case ZERO: @@ -377,10 +379,10 @@ public class GroupPane extends BorderPane { */ @FXML @NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)", - "GroupPane.bottomLabel.displayText=Group Viewing History: ", - "GroupPane.hederLabel.displayText=Tag Selected Files:", - "GroupPane.catContainerLabel.displayText=Categorize Selected File:", - "GroupPane.catHeadingLabel.displayText=Category:"}) + "GroupPane.bottomLabel.displayText=Group Viewing History: ", + "GroupPane.hederLabel.displayText=Tag Selected Files:", + "GroupPane.catContainerLabel.displayText=Categorize Selected File:", + "GroupPane.catHeadingLabel.displayText=Category:"}) void initialize() { 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'."; @@ -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 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'."; - + for (Category cat : Category.values()) { ToggleButton toggleForCategory = getToggleForCategory(cat); 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.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> { if (toggleSelected && slideShowPane != null) { - slideShowPane.getFileID().ifPresent((fileID) -> { + slideShowPane.getFileID().ifPresent(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.cellWidthProperty().bind(cellSize); gridView.setCellFactory((GridView param) -> new DrawableCell()); - + BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); - + Platform.runLater(() -> { try { TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); @@ -441,9 +443,9 @@ public class GroupPane extends BorderPane { tagSelectedSplitMenu.getItems().setAll(selTagMenues); } }); - + }); - + CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller); catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setText(cat5SelectedAction.getText()); @@ -455,12 +457,12 @@ public class GroupPane extends BorderPane { catSelectedSplitMenu.getItems().setAll(categoryMenues); } }); - + slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().add("toggle-button"); tileToggle.getStyleClass().remove("radio-button"); tileToggle.getStyleClass().add("toggle-button"); - + bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText()); headerLabel.setText(Bundle.GroupPane_hederLabel_displayText()); catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText()); @@ -480,12 +482,12 @@ public class GroupPane extends BorderPane { //listen to toggles and update view state slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get())); tileToggle.setOnAction(onAction -> activateTileViewer()); - + controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); - + addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler()); - + ActionUtils.configureButton(undoAction, undoButton); ActionUtils.configureButton(redoAction, redoButton); ActionUtils.configureButton(forwardAction, forwardButton); @@ -501,7 +503,7 @@ public class GroupPane extends BorderPane { nextButton.setEffect(null); onAction.handle(actionEvent); }); - + nextGroupAction.disabledProperty().addListener((Observable observable) -> { boolean newValue = nextGroupAction.isDisabled(); nextButton.setEffect(newValue ? null : DROP_SHADOW); @@ -521,7 +523,7 @@ public class GroupPane extends BorderPane { scrollToFileID(newFileId); } }); - + setViewState(controller.viewState().get()); } @@ -531,16 +533,16 @@ public class GroupPane extends BorderPane { if (newFileID == null) { return; //scrolling to no file doesn't make sense, so abort. } - + final ObservableList fileIds = gridView.getItems(); - + int selectedIndex = fileIds.indexOf(newFileID); if (selectedIndex == -1) { //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. return; } - + getScrollBar().ifPresent(scrollBar -> { DrawableCell cell = cellMap.get(newFileID); @@ -567,14 +569,14 @@ public class GroupPane extends BorderPane { } cell = cellMap.get(newFileID); } - + final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal()); Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal()); //while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates int i = 0; while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) { - + if (tileBounds.getMinY() < gridViewBounds.getMinY()) { scrollBar.decrement(); } else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) { @@ -592,13 +594,13 @@ public class GroupPane extends BorderPane { * @param grouping the new grouping assigned to this group */ void setViewState(GroupViewState viewState) { - + if (isNull(viewState) || isNull(viewState.getGroup())) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } this.grouping.set(null); - + Platform.runLater(() -> { gridView.getItems().setAll(Collections.emptyList()); setCenter(null); @@ -610,18 +612,18 @@ public class GroupPane extends BorderPane { cellMap.clear(); } }); - + } else { if (getGroup() != viewState.getGroup()) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } this.grouping.set(viewState.getGroup()); - + getGroup().getFileIDs().addListener(filesSyncListener); - + final String header = getHeaderString(); - + Platform.runLater(() -> { gridView.getItems().setAll(getGroup().getFileIDs()); slideShowToggle.setDisable(gridView.getItems().isEmpty()); @@ -636,14 +638,14 @@ public class GroupPane extends BorderPane { } } } - + @ThreadConfined(type = ThreadType.JFX) private void resetScrollBar() { getScrollBar().ifPresent((scrollBar) -> { scrollBar.setValue(0); }); } - + @ThreadConfined(type = ThreadType.JFX) private Optional getScrollBar() { 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 } - + void makeSelection(Boolean shiftDown, Long newFileID) { - + if (shiftDown) { //TODO: do more hear to implement slicker multiselect int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); List 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.select(newFileID); } else { @@ -668,11 +670,11 @@ public class GroupPane extends BorderPane { selectionModel.clearAndSelect(newFileID); } } - + private class DrawableCell extends GridCell { - + private final DrawableTile tile = new DrawableTile(GroupPane.this, controller); - + DrawableCell() { itemProperty().addListener((ObservableValue observable, Long oldValue, Long newValue) -> { if (oldValue != null) { @@ -688,19 +690,19 @@ public class GroupPane extends BorderPane { } } cellMap.put(newValue, DrawableCell.this); - + } }); - + setGraphic(tile); } - + @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); tile.setFile(item); } - + void resetItem() { tile.setFile(null); } @@ -711,10 +713,10 @@ public class GroupPane extends BorderPane { * arrows) */ private class KeyboardHandler implements EventHandler { - + @Override public void handle(KeyEvent t) { - + if (t.getEventType() == KeyEvent.KEY_PRESSED) { switch (t.getCode()) { case SHIFT: @@ -757,51 +759,56 @@ public class GroupPane extends BorderPane { t.consume(); break; } - + if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { selectAllFiles(); t.consume(); } - if (selectionModel.getSelected().isEmpty() == false) { - switch (t.getCode()) { - case NUMPAD0: - case DIGIT0: - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ZERO), ""); - 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; + ObservableSet selected = selectionModel.getSelected(); + if (selected.isEmpty() == false) { + Category cat = keyCodeToCat(t.getCode()); + if (cat != null) { + new CategorizeAction(controller, cat, selected).handle(null); } } } } - + + 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) { Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); - + int lastSelectedIndex = lastSelectFileId != null ? grouping.get().getFileIDs().indexOf(lastSelectFileId) : Optional.ofNullable(selectionAnchorIndex).orElse(0); - + final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1); - + final Map tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1); // implement proper keyboard based multiselect @@ -820,17 +827,17 @@ public class GroupPane extends BorderPane { } } } - + private class MouseHandler implements EventHandler { - + private ContextMenu buildContextMenu() { ArrayList menuItems = new ArrayList<>(); - - menuItems.add(new CategorizeAction(controller).getPopupMenu()); + + menuItems.add(CategorizeAction.getCategoriesMenu(controller)); menuItems.add(new AddDrawableTagAction(controller).getPopupMenu()); - + Collection menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); - + for (ContextMenuActionsProvider provider : menuProviders) { for (final Action act : provider.getActions()) { if (act instanceof Presenter.Popup) { @@ -847,12 +854,12 @@ public class GroupPane extends BorderPane { }); }); menuItems.add(extractMenuItem); - + ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{})); contextMenu.setAutoHide(true); return contextMenu; } - + @Override public void handle(MouseEvent t) { switch (t.getButton()) { @@ -873,7 +880,7 @@ public class GroupPane extends BorderPane { if (contextMenu == null) { contextMenu = buildContextMenu(); } - + contextMenu.hide(); contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY()); } From fdc58d0d35a95748d64ec24c10a52e9b1e306886 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 11 Feb 2016 14:51:26 -0500 Subject: [PATCH 06/67] make sure to kill the progressHandle and timer even if exception is thrown, eg, when case is closed handle exceptions better in VideoUtils.generateVideoThumbnail() update copyright dates put cleanup code in finally block --- .../corecomponents/ThumbnailViewNode.java | 12 +++++---- .../autopsy/coreutils/VideoUtils.java | 25 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java index dc06466d54..5ac5b95c67 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -95,16 +95,18 @@ class ThumbnailViewNode extends FilterNode { super.done(); try { iconCache = new SoftReference<>(super.get()); - progressHandle.finish(); 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) { timer.stop(); timer = null; + } - } catch (InterruptedException | ExecutionException ex) { - Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon", ex); //NON-NLS + swingWorker = null; } - swingWorker = null; } }; swingWorker.execute(); diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java index dc06b75019..efa331c1dd 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.coreutils; +import com.google.common.io.Files; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -94,20 +95,18 @@ public class VideoUtils { static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { java.io.File tempFile = getTempVideoFile(file); - try { - if (tempFile.exists() == false || tempFile.length() < file.getSize()) { - com.google.common.io.Files.createParentDirs(tempFile); - ProgressHandle progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(VideoUtils.class, "VideoUtils.genVideoThumb.progress.text", file.getName())); - progress.start(100); - try { - ContentUtils.writeToFile(file, tempFile, progress, null, true); - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Error buffering file", ex); //NON-NLS - } + if (tempFile.exists() == false || tempFile.length() < file.getSize()) { + ProgressHandle progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(VideoUtils.class, "VideoUtils.genVideoThumb.progress.text", file.getName())); + progress.start(100); + try { + Files.createParentDirs(tempFile); + ContentUtils.writeToFile(file, tempFile, progress, null, true); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Error buffering file to disk", ex); //NON-NLS + return null; + } finally { progress.finish(); } - } catch (IOException ex) { - return null; } VideoCapture videoFile = new VideoCapture(); // will contain the video From b9c35b7a4f8ea9ea09f94e76f7c3197a8c643eec Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 12 Feb 2016 16:15:37 -0500 Subject: [PATCH 07/67] clean up ImageUtils logging --- .../autopsy/coreutils/ImageUtils.java | 122 ++++++++---------- 1 file changed, 53 insertions(+), 69 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index be29d789c9..e700df5f2c 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-15 Basis Technology Corp. + * Copyright 2012-16 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -30,6 +30,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Paths; +import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -74,10 +75,6 @@ public class ImageUtils { 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 */ @@ -594,7 +591,7 @@ public class ImageUtils { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { 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)); throw iioException; } @@ -613,7 +610,7 @@ public class ImageUtils { reader.dispose(); } } 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)); throw iioException; @@ -646,18 +643,19 @@ public class ImageUtils { */ 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 File cacheFile; private final boolean defaultOnFailure; -// @NbBundle.Messages({"# {0} - file name", -// "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", "# {0} - file name", -// "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"}) + @NbBundle.Messages({"# {0} - file name", + "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", + "# {0} - file name", + "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for video {0}"}) private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) { super(file); - updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.loadingThumbnailFor", file.getName())); + updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName())); this.iconSize = iconSize; this.defaultOnFailure = defaultOnFailure; this.cacheFile = getCachedThumbnailLocation(file.getId()); @@ -678,36 +676,39 @@ public class ImageUtils { if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) { return SwingFXUtils.toFXImage(cachedThumbnail, null); } - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS + } catch (Exception ex) { + 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()) { return null; } + //There was no correctly-sized cached thumbnail so make one. BufferedImage thumbnail = null; - if (VideoUtils.isVideoThumbnailSupported(file)) { if (openCVLoaded) { - updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.generatingPreviewFor", file.getName())); + updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName())); thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); } if (null == thumbnail) { if (defaultOnFailure) { thumbnail = DEFAULT_THUMBNAIL; } else { - throw new IIOException("Failed to generate thumbnail for video file."); + throw new IIOException("Failed to generate a thumbnail for " + getContentPathSafe(file)); } } } else { //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); if (null == bufferedImage) { - LOGGER.log(Level.WARNING, FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION); - throw new IIOException(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION); + String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file)); + LOGGER.log(Level.WARNING, msg); + throw new IIOException(msg); } updateProgress(-1, 1); @@ -716,23 +717,21 @@ public class ImageUtils { thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); } catch (IllegalArgumentException | OutOfMemoryError e) { // 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 width = bufferedImage.getWidth(); if (iconSize < height || iconSize < width) { final int cropHeight = Math.min(iconSize, height); final int cropWidth = Math.min(iconSize, width); - try { thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight); } catch (Exception cropException) { - LOGGER.log(Level.WARNING, "Could not crop image {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS - throw cropException; + LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS } } } 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; } } @@ -744,7 +743,7 @@ public class ImageUtils { updateProgress(-1, 1); //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); } @@ -790,16 +789,16 @@ public class ImageUtils { /** * A task that reads the content of a AbstractFile as a javafx Image. */ + @NbBundle.Messages({ + "# {0} - file name", + "LoadImageTask.mesageText=Reading image: {0}"}) static private class ReadImageTask extends ReadImageTaskBase { ReadImageTask(AbstractFile file) { super(file); - updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.ReadImageTask.mesage.text", file.getName())); + updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); } -// @NbBundle.Messages({ -// "# {0} - file name", -// "LoadImageTask.mesageText=Reading image: {0}"}) @Override protected javafx.scene.image.Image call() throws Exception { return readImage(); @@ -811,70 +810,55 @@ public class ImageUtils { */ static private abstract class ReadImageTaskBase extends Task 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; - private ImageReader reader; +// private ImageReader reader; ReadImageTaskBase(AbstractFile file) { this.file = file; } protected javafx.scene.image.Image readImage() throws IOException { - try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { - if (ImageUtils.isGIF(file)) { - //use JavaFX to directly read GIF to preserve potential animation, - javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(inputStream)); - if (image.isError() == false) { - return image; - } - //fall through to default image reading code if there was an error + if (ImageUtils.isGIF(file)) { + //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))); + if (image.isError() == false) { + return image; } - if (isCancelled()) { - return null; - } - try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { - if (input == null) { - throw new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM); - } - Iterator readers = ImageIO.getImageReaders(input); + //fall through to default image reading code if there was an error + } + if (isCancelled()) { + return null; + } - //we use the first ImageReader, is there any point to trying the others? - if (readers.hasNext()) { - reader = readers.next(); - reader.addIIOReadProgressListener(this); - reader.setInput(input); + return getImageProperty(file, "ImageIO could not read {0}: ", + imageReader -> { + imageReader.addIIOReadProgressListener(ReadImageTaskBase.this); /* * This is the important part, get or create a * ImageReadParam, create a destination image to hold * the decoded result, then pass that image with the * param. */ - ImageReadParam param = reader.getDefaultReadParam(); - - BufferedImage bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); + ImageReadParam param = imageReader.getDefaultReadParam(); + BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0)); param.setDestination(bufferedImage); 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) { - // Ignore this exception or display a warning or similar, for exceptions happening during decoding - LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N + LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N } finally { - reader.removeIIOReadProgressListener(this); - reader.dispose(); + imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this); } if (isCancelled()) { return null; } return SwingFXUtils.toFXImage(bufferedImage, null); - } else { - throw new IIOException(NO_IMAGE_READER_FOUND_FOR_ + ImageUtils.getContentPathSafe(file)); - } - } - } + }); } @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 updateProgress(percentageDone, 100); if (isCancelled()) { @@ -890,11 +874,11 @@ public class ImageUtils { try { javafx.scene.image.Image fxImage = get(); 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 { if (fxImage.isError()) { //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) { @@ -905,7 +889,7 @@ public class ImageUtils { @Override protected void 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 From 3372c003f321930f0d9efeb6657648ec31c9a397 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 12 Feb 2016 16:33:42 -0500 Subject: [PATCH 08/67] cleanup Bundle.Properties and logging in VideoUtils.java --- .../sleuthkit/autopsy/coreutils/Bundle.properties | 6 +----- .../org/sleuthkit/autopsy/coreutils/ImageUtils.java | 8 ++++---- .../org/sleuthkit/autopsy/coreutils/VideoUtils.java | 12 ++++++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties index 643a4b75f1..f633003d1d 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties @@ -20,8 +20,4 @@ PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, fre PlatformUtil.getAllMemUsageInfo.usageText={0}\n\ {1}\n\ Process Virtual Memory\: {2} -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} +StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index e700df5f2c..4e627ea09d 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -652,7 +652,7 @@ public class ImageUtils { @NbBundle.Messages({"# {0} - file name", "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", "# {0} - file name", - "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for video {0}"}) + "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"}) private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) { super(file); updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName())); @@ -791,12 +791,12 @@ public class ImageUtils { */ @NbBundle.Messages({ "# {0} - file name", - "LoadImageTask.mesageText=Reading image: {0}"}) + "ReadImageTask.mesageText=Reading image: {0}"}) static private class ReadImageTask extends ReadImageTaskBase { ReadImageTask(AbstractFile file) { super(file); - updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); + updateMessage(Bundle.ReadImageTask_mesageText(file.getName())); } @Override @@ -934,7 +934,7 @@ public class ImageUtils { * * @return */ - private static String getContentPathSafe(Content content) { + static String getContentPathSafe(Content content) { try { return content.getUniquePath(); } catch (TskCoreException tskCoreException) { diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java index efa331c1dd..58c61e955b 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java @@ -92,18 +92,18 @@ public class VideoUtils { 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) { java.io.File tempFile = getTempVideoFile(file); - if (tempFile.exists() == false || tempFile.length() < file.getSize()) { - ProgressHandle progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(VideoUtils.class, "VideoUtils.genVideoThumb.progress.text", file.getName())); + ProgressHandle progress = ProgressHandleFactory.createHandle(Bundle.VideoUtils_genVideoThumb_progress_text(file.getName())); progress.start(100); try { Files.createParentDirs(tempFile); ContentUtils.writeToFile(file, tempFile, progress, null, true); } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Error buffering file to disk", ex); //NON-NLS - return null; + LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS } finally { progress.finish(); } @@ -112,11 +112,13 @@ public class VideoUtils { VideoCapture videoFile = new VideoCapture(); // will contain the video if (!videoFile.open(tempFile.toString())) { + LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS return null; } double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames if (fps <= 0 || totalFrames <= 0) { + LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS return null; } double milliseconds = 1000 * (totalFrames / fps); //total milliseconds @@ -131,10 +133,12 @@ public class VideoUtils { for (int x = 0; x < THUMB_COLUMNS; x++) { for (int y = 0; y < THUMB_ROWS; y++) { 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 } //read the frame into the image/matrix 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 } From 5ba21cf3c32a6e760fb530267eb63f9da2a6d68c Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 29 Jan 2016 15:15:19 -0500 Subject: [PATCH 09/67] add new sort order buttons, add new SortChooser control, refactor GroupSortBy to implement Comparator directly, refactor ToolBar, GroupManager etc accordingly and to use new SortChooser --- .../imagegallery/datamodel/DrawableDB.java | 15 +- .../datamodel/grouping/GroupManager.java | 39 ++-- .../datamodel/grouping/GroupSortBy.java | 120 +++--------- .../autopsy/imagegallery/gui/SortChooser.fxml | 57 ++++++ .../autopsy/imagegallery/gui/SortChooser.java | 178 ++++++++++++++++++ .../autopsy/imagegallery/gui/Toolbar.fxml | 169 +++++++---------- .../autopsy/imagegallery/gui/Toolbar.java | 80 +++----- .../imagegallery/images/sort_asc_az.png | Bin 0 -> 699 bytes .../imagegallery/images/sort_ascending.png | Bin 0 -> 768 bytes .../imagegallery/images/sort_desc_az.png | Bin 0 -> 714 bytes .../imagegallery/images/sort_descending.png | Bin 0 -> 707 bytes 11 files changed, 384 insertions(+), 274 deletions(-) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.fxml create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_asc_az.png create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_ascending.png create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_desc_az.png create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_descending.png diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 034fbe3b03..cb842ed1df 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -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 String orderByClause = ""; - switch (sortBy) { - case GROUP_BY_VALUE: - orderByClause = " ORDER BY " + groupBy.attrName.toString(); //NON-NLS - break; - case FILE_COUNT: - orderByClause = " ORDER BY COUNT(*)"; //NON-NLS - break; - case NONE: -// case PRIORITY: - break; + + if (sortBy == GROUP_BY_VALUE) { + orderByClause = " ORDER BY " + groupBy.attrName.toString(); + } else if (sortBy == GroupSortBy.FILE_COUNT) { + orderByClause = " ORDER BY COUNT(*)"; } query.append(orderByClause); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index e1f497a295..bdc659e416 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -126,7 +127,7 @@ public class GroupManager { private volatile DrawableAttribute groupBy = DrawableAttribute.PATH; private volatile SortOrder sortOrder = SortOrder.ASCENDING; - private final ReadOnlyObjectWrapper sortByProp = new ReadOnlyObjectWrapper<>(sortBy); + private final ReadOnlyObjectWrapper< Comparator> sortByProp = new ReadOnlyObjectWrapper<>(sortBy); private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(groupBy); private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder); @@ -274,7 +275,7 @@ public class GroupManager { } else if (unSeenGroups.contains(group) == false) { unSeenGroups.add(group); } - FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder)); + FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); } /** @@ -299,11 +300,11 @@ public class GroupManager { Platform.runLater(() -> { if (analyzedGroups.contains(group)) { analyzedGroups.remove(group); - FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder)); + FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); } if (unSeenGroups.contains(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 getSortBy() { return sortBy; } @@ -459,7 +460,7 @@ public class GroupManager { Platform.runLater(() -> sortByProp.set(sortBy)); } - public ReadOnlyObjectProperty getSortByProperty() { + public ReadOnlyObjectProperty< Comparator> getSortByProperty() { return sortByProp.getReadOnlyProperty(); } @@ -523,8 +524,8 @@ public class GroupManager { setSortBy(sortBy); setSortOrder(sortOrder); Platform.runLater(() -> { - FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder)); - FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder)); + FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); + FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); }); } } @@ -675,7 +676,7 @@ public class GroupManager { if (analyzedGroups.contains(group) == false) { analyzedGroups.add(group); if (Objects.isNull(task)) { - FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder)); + FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); } } markGroupSeen(group, groupSeen); @@ -735,8 +736,8 @@ public class GroupManager { private final SortOrder sortOrder; - public ReGroupTask(DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { - super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.name(), sortOrder.toString()), true); + ReGroupTask(DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { + super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); this.groupBy = groupBy; this.sortBy = sortBy; @@ -755,7 +756,7 @@ public class GroupManager { 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(() -> { analyzedGroups.clear(); unSeenGroups.clear(); @@ -778,7 +779,7 @@ public class GroupManager { groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); popuplateIfAnalyzed(new GroupKey(groupBy, val), this); } - Platform.runLater(() -> FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder))); + Platform.runLater(() -> FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy))); updateProgress(1, 1); return null; @@ -793,4 +794,16 @@ public class GroupManager { } } } + + private static Comparator applySortOrder(final SortOrder sortOrder, Comparator comparator) { + switch (sortOrder) { + case ASCENDING: + return comparator; + case DESCENDING: + return comparator.reversed(); + case UNSORTED: + default: + return new GroupSortBy.AllEqualComparator<>(); + } + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java index 5d8740b343..8239837d6a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java @@ -18,89 +18,50 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; -import java.util.Arrays; import java.util.Comparator; import javafx.collections.FXCollections; import javafx.collections.ObservableList; 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.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 - * down in Toolbar as well as each enum value having the stategy - * ({@link Comparator}) for sorting the groups + * Pseudo enum of possible properties to sort groups by. */ @NbBundle.Messages({"GroupSortBy.groupSize=Group Size", - "GroupSortBy.groupName=Group Name", - "GroupSortBy.none=None", - "GroupSortBy.priority=Priority"}) -public enum GroupSortBy implements ComparatorProvider { + "GroupSortBy.groupName=Group Name", + "GroupSortBy.none=None", + "GroupSortBy.priority=Priority"}) +public class GroupSortBy implements Comparator { /** - * sort the groups by the number of files in each sort the groups by the - * number of files in each + * sort the groups by the number of files in each */ - FILE_COUNT(Bundle.GroupSortBy_groupSize(), true, "folder-open-image.png") { //NON-NLS - @Override - public Comparator getGrpComparator(final SortOrder sortOrder) { - return applySortOrder(sortOrder, Comparator.comparingInt(DrawableGroup::getSize)); - } + public final static GroupSortBy FILE_COUNT = new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png", Comparator.comparing(DrawableGroup::getSize)); - @Override - public > Comparator getValueComparator(final DrawableAttribute attr, final SortOrder sortOrder) { - return getDefaultValueComparator(attr, sortOrder); - } - }, /** * sort the groups by the natural order of the grouping value ( eg group * them by path alphabetically ) */ - GROUP_BY_VALUE(Bundle.GroupSortBy_groupName(), true, "folder-rename.png") { //NON-NLS - @Override - public Comparator getGrpComparator(final SortOrder sortOrder) { - return applySortOrder(sortOrder, Comparator.comparing(t -> t.getGroupByValueDislpayName())); - } + public final static GroupSortBy GROUP_BY_VALUE = new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png", Comparator.comparing(DrawableGroup::getGroupByValueDislpayName)); - @Override - public > Comparator getValueComparator(final DrawableAttribute attr, final SortOrder sortOrder) { - return applySortOrder(sortOrder, Comparator.naturalOrder()); - } - }, /** * don't sort the groups just use what ever order they come in (ingest * order) */ - NONE(Bundle.GroupSortBy_none(), false, "prohibition.png") { //NON-NLS - @Override - public Comparator getGrpComparator(SortOrder sortOrder) { - return new NoOpComparator<>(); - } + public final static GroupSortBy NONE = new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png", new AllEqualComparator<>()); - @Override - public > Comparator getValueComparator(DrawableAttribute attr, final SortOrder sortOrder) { - return new NoOpComparator<>(); - } - }, /** * sort the groups by some priority metric to be determined and implemented */ - PRIORITY(Bundle.GroupSortBy_priority(), false, "hashset_hits.png") { //NON-NLS - @Override - public Comparator getGrpComparator(SortOrder sortOrder) { - return Comparator.nullsLast(Comparator.comparingDouble(DrawableGroup::getHashHitDensity).thenComparingInt(DrawableGroup::getSize).reversed()); - } + 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 getValueComparator(DrawableAttribute attr, SortOrder sortOrder) { - return getDefaultValueComparator(attr, sortOrder); - } - }; + @Override + public int compare(DrawableGroup o1, DrawableGroup o2) { + return delegate.compare(o1, o2); + } + + private final static ObservableList values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT)); /** * get a list of the values of this enum @@ -108,8 +69,7 @@ public enum GroupSortBy implements ComparatorProvider { * @return */ public static ObservableList getValues() { - return FXCollections.observableArrayList(Arrays.asList(values())); - + return values; } final private String displayName; @@ -118,12 +78,12 @@ public enum GroupSortBy implements ComparatorProvider { private final String imageName; - private final Boolean sortOrderEnabled; + private final Comparator delegate; - private GroupSortBy(String displayName, Boolean sortOrderEnabled, String imagePath) { + private GroupSortBy(String displayName, String imagePath, Comparator internalComparator) { this.displayName = displayName; - this.sortOrderEnabled = sortOrderEnabled; this.imageName = imagePath; + this.delegate = internalComparator; } public String getDisplayName() { @@ -139,49 +99,11 @@ public enum GroupSortBy implements ComparatorProvider { return icon; } - public Boolean isSortOrderEnabled() { - return sortOrderEnabled; - } - - private static Comparator applySortOrder(final SortOrder sortOrder, Comparator comparator) { - switch (sortOrder) { - case ASCENDING: - return comparator; - case DESCENDING: - return comparator.reversed(); - case UNSORTED: - default: - return new NoOpComparator<>(); - } - } - - private static class NoOpComparator implements Comparator { + static class AllEqualComparator implements Comparator { @Override public int compare(A o1, A o2) { 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 { - - > Comparator getValueComparator(DrawableAttribute attr, SortOrder sortOrder); - - Comparator getGrpComparator(SortOrder sortOrder); - - default > Comparator getDefaultValueComparator(DrawableAttribute 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); - }; - } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.fxml new file mode 100644 index 0000000000..432b47d8c7 --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.fxml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java new file mode 100644 index 0000000000..2486615a11 --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java @@ -0,0 +1,178 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 Basis Technology Corp. + * Contact: carrier sleuthkit 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.binding.ObjectBinding; +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.openide.util.Exceptions; +import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; + +/** + * + */ +public class SortChooser> extends HBox { + + @FXML + private RadioButton ascRadio; + @FXML + private RadioButton descRadio; + @FXML + private ToggleGroup orderGroup; + @FXML + private ComboBox sortByBox; + + private final ObservableList comparators; + + private final ReadOnlyObjectWrapper sortOrder = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); + private final SimpleBooleanProperty sortOrderDisabled = new SimpleBooleanProperty(false); + private final SimpleObjectProperty valueType = new SimpleObjectProperty<>(ValueType.NUMERIC); + private ObjectBinding comparator; + + public SortChooser(ObservableList comps) { + this.comparators = comps; + FXMLConstructor.construct(this, "SortChooser.fxml"); + } + + public ValueType getValueType() { + return valueType.get(); + } + + public void setValueType(ValueType type) { + valueType.set(type); + } + + public SimpleObjectProperty valueTypeProperty() { + return valueType; + } + + @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); + + orderGroup.selectedToggleProperty().addListener(selectedToggle -> { + sortOrder.set(orderGroup.getSelectedToggle() == ascRadio ? SortOrder.ASCENDING : SortOrder.DESCENDING); + }); + + sortByBox.setItems(comparators); + sortByBox.setCellFactory(listView -> new ComparatorCell()); + sortByBox.setButtonCell(new ComparatorCell()); + } + + void setSortOrderDisabled(boolean disabled) { + sortOrderDisabled.set(disabled); + } + + public boolean isSortOrderDisabled() { + return sortOrderDisabled.get(); + } + + SimpleBooleanProperty sortOrderDisabledProperty() { + return sortOrderDisabled; + } + + SortOrder getSortOrder() { + return sortOrder.get(); + } + + ReadOnlyObjectProperty sortOrderProperty() { + return sortOrder.getReadOnlyProperty(); + } + + Y getComparator() { + return sortByBox.getSelectionModel().getSelectedItem(); + } + + void setComparator(Y selected) { + sortByBox.getSelectionModel().select(selected); + } + + ReadOnlyObjectProperty 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 { + + @Override + protected void updateItem(Y item, boolean empty) { + super.updateItem(item, empty); //To change body of generated methods, choose Tools | Templates. + try { + String displayName = (String) item.getClass().getMethod("getDisplayName").invoke(item); + setText(displayName); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + Exceptions.printStackTrace(ex); + setText(item.toString()); + } catch (NullPointerException ex) { + setText("NPE"); + } + } + } +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml index d161cc99f9..aba7982201 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml @@ -1,113 +1,86 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index f6ca84b3c7..cd6cda7623 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -32,12 +32,9 @@ import javafx.fxml.FXML; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; -import javafx.scene.control.RadioButton; import javafx.scene.control.Slider; import javafx.scene.control.SplitMenuButton; -import javafx.scene.control.ToggleGroup; import javafx.scene.control.ToolBar; -import javafx.scene.layout.HBox; import javax.swing.SortOrder; import org.openide.util.NbBundle; @@ -48,6 +45,7 @@ import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction; import org.sleuthkit.autopsy.imagegallery.actions.TagGroupAction; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; 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.GroupViewState; import org.sleuthkit.datamodel.TskCoreException; @@ -67,21 +65,6 @@ public class Toolbar extends ToolBar { @FXML private Slider sizeSlider; - @FXML - private ComboBox sortByBox; - - @FXML - private RadioButton ascRadio; - - @FXML - private RadioButton descRadio; - - @FXML - private ToggleGroup orderGroup; - - @FXML - private HBox sortControlGroup; - @FXML private SplitMenuButton catGroupMenuButton; @@ -91,8 +74,7 @@ public class Toolbar extends ToolBar { @FXML private Label groupByLabel; - @FXML - private Label sortByLabel; + @FXML private Label tagImageViewLabel; @@ -107,20 +89,18 @@ public class Toolbar extends ToolBar { private final SimpleObjectProperty 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 SortChooser sortChooser; - synchronized public SortOrder getSortOrder() { - return orderProperty.get(); - } + private final InvalidationListener queryInvalidationListener = new InvalidationListener() { + public void invalidated(Observable o) { + controller.getGroupManager().regroup( + groupByBox.getSelectionModel().getSelectedItem(), + sortChooser.getComparator(), + sortChooser.getSortOrder(), + false); + } + }; public DoubleProperty sizeSliderValue() { return sizeSlider.valueProperty(); @@ -142,15 +122,9 @@ public class Toolbar extends ToolBar { "Toolbar.categoryImageViewLabel=Categorize Group's Files:", "Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"}) 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 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 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 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'."; controller.viewState().addListener((observable, oldViewState, newViewState) -> { @@ -175,9 +149,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()); @@ -201,21 +172,22 @@ public class Toolbar extends ToolBar { groupByBox.setCellFactory(listView -> new AttributeListCell()); groupByBox.setButtonCell(new AttributeListCell()); - sortByBox.setCellFactory(listView -> new SortByListCell()); - sortByBox.setButtonCell(new SortByListCell()); - sortByBox.setItems(GroupSortBy.getValues()); - - sortByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); - - sortByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + sortChooser = new SortChooser<>(GroupSortBy.getValues()); + sortChooser.comparatorProperty().addListener((observable, oldValue, newValue) -> { final boolean orderEnabled = newValue == GroupSortBy.NONE || newValue == GroupSortBy.PRIORITY; - ascRadio.setDisable(orderEnabled); - descRadio.setDisable(orderEnabled); + sortChooser.setSortOrderDisabled(orderEnabled); + final SortChooser.ValueType valueType = newValue == 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) { @@ -230,8 +202,8 @@ public class Toolbar extends ToolBar { public void reset() { Platform.runLater(() -> { groupByBox.getSelectionModel().select(DrawableAttribute.PATH); - sortByBox.getSelectionModel().select(GroupSortBy.NONE); - orderGroup.selectToggle(ascRadio); +// sortByBox.getSelectionModel().select(GroupSortBy.NONE); +// orderGroup.selectToggle(ascRadio); sizeSlider.setValue(SIZE_SLIDER_DEFAULT); }); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_asc_az.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_asc_az.png new file mode 100644 index 0000000000000000000000000000000000000000..0cac09a37724d2a05fe152f0a81e714caeb95165 GIT binary patch literal 699 zcmV;s0!00ZP)0!T?j zK~y-)b&*X-RACf`pYPr~g5#ovXd9tTL>rxK#zmvz6#0WmV_QRvqJp4Jppl9}8Auz6 z+87kmCc;JJs;Q{NG$Cg|vqe7}BU8c>Md$b4@B7*~)BM=GIvmdXp7*>*u!E{wFP=%I zgF=957!yDGTRO=>%W&9_Va%X7@N%%_Tc&Sh3PC9kS<8ktVs z&h2Lflu}5kd9$<)Xo{fp$*t9;hg!>$ zE2GhW>5)7C1PvvoK}D&F$`SBg4C$B@lf^Kva%QYSKw;$EGLXXDh9F`hJ!Y%fe+EEDy8_zzak}&4I{~fu}`zZhb002ovPDHLkV1klyJmvrZ literal 0 HcmV?d00001 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_ascending.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_ascending.png new file mode 100644 index 0000000000000000000000000000000000000000..5379d494bf4c50903c6f37e0daca9856520010ef GIT binary patch literal 768 zcmV+b1ONPqP)y=-&VU0*y&T zK~y-)rITH6%~=%2e{28uzZFW$t;8pg5UmIqsfMc89MvlyK)FC-GGiDKsh5_z5J4nD zd;sw>Q$^EKE>ud7WD<8XH`69_F{!DSOpbH*UVFJHM~OT8X6>wtC(rX+Yl)fhJoKH~*7>14t1_LlI_5Fbel%tV*4+iIM zy}(({+jxPY6H7XQPr0Ysd$(3SRJEGB?%KYNmE3swAOHUO7ehzd1fZjLH8IV;5u$BR z$#Bk*dq0&qw*bJm^$e2CsDwyK1@AKqV|~vsMjd9q5MtVXzViT46S?>7y?kcA6G24q z%&<~cqb8q@fO+cue$?h5z+{@>*?1p$=Dz^xt2qqHD&$#)k*bhH1TE|Ev8&fx%!EuT zXem6bnU__^_(WA#sVeUkGVjo+!2kBfPCl8NcmHtz_~1^UVa5VZZ9BaMCxWayao;v? zYNiqb<#OOvDY3AjmOQWJ&fQV!Yq~{~yBVUkYx?Xgn}#_eKx0c+|Jd?1tBD~Z0-5(b ze>B4I(G5-Qr#BVLLG66!PlTwzG`!qr?7e&j-_|o&95~e4L|6aV%C*e^Jbmz(;iH?I zI(l1*$v_Y$(*&dAzauyVA@^?j-_|n-z`&u_#&yTKs4Ag$(n)L(T@Gdf4G&p9n}raPm3%kRAJbH4MQb2PT=V9$%E zLaIhh14GxJEwtC3ORqhtHt;8b$zO+b6i4cmh`3*pM{WOnSCSdiSIX>zLMN-JQuIRUW0J>MG>58I& zFUJmmb^ml}sUuXKEff=rt2cnvEPw(G-JmevK&hPwTgZM}D%Mi&yMZqYqnRwc0KcxW zKj0_Vf2ZthHDYq*HPUz6mpY^<>nunP|qm zJ$`U;`qjm^z0GF)*Y`e6QW?D5AMl1~Ek#0RY<7y+>=a9pP{w@n(gP)In}QOv2C(+* wD+){7Mmp{$IX@JjEDhYHw+hSyrQVJI0Bm(0pCb1HwEzGB07*qoM6N<$f;Z1I?*IS* literal 0 HcmV?d00001 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_descending.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/sort_descending.png new file mode 100644 index 0000000000000000000000000000000000000000..f63a65fb9d16efbbfb557e3c768199dec20f48bc GIT binary patch literal 707 zcmV;!0zCbRP) z68sAU4-E(+ElR7gMPj8F{{Y*Ac+>i$l|ozEVh{aQ+u0iz%nZ@^+#6Jin? zG5gF9WJW605jvd16{0H4#uO2dc=Gxg{QT|Xd44D3IN?E4p%FhByKU|iVPK4r*ukwA zOUW<+UCOq?@Ury76wz8?dn7?ceMf08M;^C(S8(f#gR|OZjz(MrjC?3ENihZK$>PlN z%6Y9>Cm|g@Ur3MBu*uqwKD4N4Gk+aZRd;wGgz zg3RL367@f#2qF*RRS4p7JB9FQxayKY5F8EN)tQAjVjvP|TOq+a##spSpan8dwkojt z05$m-aekK$3JdY;ULLL^EWGw>J7Y8=k1B8-Q-PvbK*A=O4_Vh6Gs~|oXwBhJjNolX z;^CZyQhG~Dd6oAt|WaI%0RBXi$*r*W@gHHsoznzH}rqZcgegpdrO4|$}R>;rgi}*0r zm(p4gL_L}vzk{da69iKBb^ky7(!zSVI6a)&-u#T7o(Q7R2#jBxPNxRvx61~TfmTs_ zSlfN}7eLzrC9uxg{lVUW+4tX!UwC*qb#eYnIU(-Z@4#=eUHS-58ll73FXEKwo?iFr pZQaX<;-|=>E#DB{X+QWQzyKaR4#USkGZO#+002ovPDHLkV1oN=LXrRg literal 0 HcmV?d00001 From e8654a013e7de5c5e86d5e3b8c65c64eb88480c3 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 29 Jan 2016 17:07:53 -0500 Subject: [PATCH 10/67] use new SortChooser in the left hand nav area, and refactor as necessary --- .../ImageGalleryTopComponent.java | 5 +- .../autopsy/imagegallery/gui/SortChooser.java | 67 +++++++++-------- .../autopsy/imagegallery/gui/Toolbar.java | 6 +- .../gui/navpanel/GroupComparators.java | 21 +++--- .../imagegallery/gui/navpanel/GroupTree.java | 3 +- .../gui/navpanel/HashHitGroupList.java | 3 +- .../imagegallery/gui/navpanel/NavPanel.fxml | 55 +------------- .../imagegallery/gui/navpanel/NavPanel.java | 74 ++++++------------- 8 files changed, 79 insertions(+), 155 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index e927312ee3..0ccf7542f3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -157,14 +157,15 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl hashHitList = new HashHitGroupList(controller); TabPane tabPane = new TabPane(groupTree, hashHitList); - + tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE); + tabPane.setMinWidth(TabPane.USE_PREF_SIZE); VBox.setVgrow(tabPane, Priority.ALWAYS); leftPane = new VBox(tabPane, new SummaryTablePane(controller)); SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); 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().setShowTree(() -> tabPane.getSelectionModel().select(groupTree)); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java index 2486615a11..49543d5f2d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.imagegallery.gui; import java.lang.reflect.InvocationTargetException; import java.util.Comparator; -import javafx.beans.binding.ObjectBinding; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleBooleanProperty; @@ -35,7 +34,6 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javax.swing.SortOrder; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; /** @@ -57,25 +55,12 @@ public class SortChooser> extends HBox { private final ReadOnlyObjectWrapper sortOrder = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); private final SimpleBooleanProperty sortOrderDisabled = new SimpleBooleanProperty(false); private final SimpleObjectProperty valueType = new SimpleObjectProperty<>(ValueType.NUMERIC); - private ObjectBinding comparator; public SortChooser(ObservableList comps) { this.comparators = comps; FXMLConstructor.construct(this, "SortChooser.fxml"); } - public ValueType getValueType() { - return valueType.get(); - } - - public void setValueType(ValueType type) { - valueType.set(type); - } - - public SimpleObjectProperty valueTypeProperty() { - return valueType; - } - @FXML void initialize() { assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'Toolbar.fxml'."; @@ -95,8 +80,7 @@ public class SortChooser> extends HBox { ascRadio.disableProperty().bind(sortOrderDisabled); descRadio.disableProperty().bind(sortOrderDisabled); - - orderGroup.selectedToggleProperty().addListener(selectedToggle -> { + ascRadio.selectedProperty().addListener(selectedToggle -> { sortOrder.set(orderGroup.getSelectedToggle() == ascRadio ? SortOrder.ASCENDING : SortOrder.DESCENDING); }); @@ -105,7 +89,19 @@ public class SortChooser> extends HBox { sortByBox.setButtonCell(new ComparatorCell()); } - void setSortOrderDisabled(boolean disabled) { + public ValueType getValueType() { + return valueType.get(); + } + + public void setValueType(ValueType type) { + valueType.set(type); + } + + public SimpleObjectProperty valueTypeProperty() { + return valueType; + } + + public void setSortOrderDisabled(boolean disabled) { sortOrderDisabled.set(disabled); } @@ -113,27 +109,27 @@ public class SortChooser> extends HBox { return sortOrderDisabled.get(); } - SimpleBooleanProperty sortOrderDisabledProperty() { + public SimpleBooleanProperty sortOrderDisabledProperty() { return sortOrderDisabled; } - SortOrder getSortOrder() { + public SortOrder getSortOrder() { return sortOrder.get(); } - ReadOnlyObjectProperty sortOrderProperty() { + public ReadOnlyObjectProperty sortOrderProperty() { return sortOrder.getReadOnlyProperty(); } - Y getComparator() { + public Y getComparator() { return sortByBox.getSelectionModel().getSelectedItem(); } - void setComparator(Y selected) { + public void setComparator(Y selected) { sortByBox.getSelectionModel().select(selected); } - ReadOnlyObjectProperty comparatorProperty() { + public ReadOnlyObjectProperty comparatorProperty() { return sortByBox.getSelectionModel().selectedItemProperty(); } @@ -164,14 +160,21 @@ public class SortChooser> extends HBox { @Override protected void updateItem(Y item, boolean empty) { super.updateItem(item, empty); //To change body of generated methods, choose Tools | Templates. - try { - String displayName = (String) item.getClass().getMethod("getDisplayName").invoke(item); - setText(displayName); - } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - Exceptions.printStackTrace(ex); - setText(item.toString()); - } catch (NullPointerException ex) { - setText("NPE"); + + 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); + } } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index cd6cda7623..9cfa399d3d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -173,11 +173,11 @@ public class Toolbar extends ToolBar { groupByBox.setButtonCell(new AttributeListCell()); sortChooser = new SortChooser<>(GroupSortBy.getValues()); - sortChooser.comparatorProperty().addListener((observable, oldValue, newValue) -> { - final boolean orderEnabled = newValue == GroupSortBy.NONE || newValue == GroupSortBy.PRIORITY; + sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { + final boolean orderEnabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; sortChooser.setSortOrderDisabled(orderEnabled); - final SortChooser.ValueType valueType = newValue == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; + final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; sortChooser.setValueType(valueType); queryInvalidationListener.invalidated(observable); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java index a5bbb40f49..16ac00d0a2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java @@ -18,21 +18,18 @@ */ package org.sleuthkit.autopsy.imagegallery.gui.navpanel; -import com.google.common.collect.ImmutableList; import java.util.Comparator; import java.util.function.Function; - +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; -/** - * - */ @NbBundle.Messages({"GroupComparators.uncategorizedCount=Uncategorized Count", - "GroupComparators.groupName=Group Name", - "GroupComparators.hitCount=Hit Count", - "GroupComparators.groupSize=Group Size", - "GroupComparators.hitDensity=Hit Density"}) + "GroupComparators.groupName=Group Name", + "GroupComparators.hitCount=Hit Count", + "GroupComparators.groupSize=Group Size", + "GroupComparators.hitDensity=Hit Density"}) final class GroupComparators> implements Comparator { static final GroupComparators UNCATEGORIZED_COUNT = @@ -50,10 +47,10 @@ final class GroupComparators> implements Comparator HIT_FILE_RATIO = new GroupComparators<>(Bundle.GroupComparators_hitDensity(), DrawableGroup::getHashHitDensity, density -> String.format("%.2f", density) + "%", true); //NON-NLS - private final static ImmutableList> values = ImmutableList.of(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO); + private final static ObservableList> values = FXCollections.observableArrayList(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO); - public static ImmutableList> getValues() { - return values; + public static ObservableList> getValues() { + return FXCollections.unmodifiableObservableList(values); } private final Function extractor; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index 5062f1ba37..f874aa4c69 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -71,8 +71,8 @@ final public class GroupTree extends NavPanel> { BooleanBinding groupedByPath = Bindings.equal(getGroupManager().getGroupByProperty(), DrawableAttribute.PATH); getToolBar().visibleProperty().bind(groupedByPath.not()); getToolBar().managedProperty().bind(groupedByPath.not()); - GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), getSortByBox().getSelectionModel().selectedItemProperty()); + GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), comparatorProperty()); groupTree.setCellFactory(groupCellFactory::getTreeCell); groupTree.setShowRoot(false); @@ -152,4 +152,5 @@ final public class GroupTree extends NavPanel> { return Arrays.asList(stripStart); } } + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java index a7fd97723b..3799f943ef 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/HashHitGroupList.java @@ -78,7 +78,8 @@ final public class HashHitGroupList extends NavPanel { getBorderPane().setCenter(groupList); sorted = getController().getGroupManager().getAnalyzedGroups().filtered((DrawableGroup t) -> t.getHashSetHitsCount() > 0).sorted(getDefaultComparator()); - GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), getSortByBox().getSelectionModel().selectedItemProperty()); + + GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), comparatorProperty()); groupList.setCellFactory(groupCellFactory::getListCell); groupList.setItems(sorted); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.fxml index 92a25b3421..61d6ab2dcd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.fxml @@ -1,63 +1,14 @@ - - - - - - - - - - - - + - + - - - - + diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index 6a25953c49..5d937be412 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -22,15 +22,13 @@ import com.google.common.eventbus.Subscribe; import java.util.Comparator; import java.util.Optional; import java.util.function.Function; +import javafx.beans.property.ReadOnlyObjectProperty; import javafx.fxml.FXML; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.control.RadioButton; import javafx.scene.control.SelectionModel; import javafx.scene.control.Tab; -import javafx.scene.control.ToggleGroup; import javafx.scene.control.ToolBar; import javafx.scene.layout.BorderPane; +import javax.swing.SortOrder; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; @@ -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.GroupManager; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; +import org.sleuthkit.autopsy.imagegallery.gui.SortChooser; /** * Base class for Tabs in the left hand Navigation/Context area. @@ -50,24 +49,10 @@ abstract class NavPanel extends Tab { @FXML private ToolBar toolBar; - @FXML - private ComboBox> sortByBox; - - @FXML - private RadioButton ascRadio; - - @FXML - private ToggleGroup orderGroup; - - @FXML - private RadioButton descRadio; - - @FXML - private Label sortByBoxLabel; - private final ImageGalleryController controller; private final GroupManager groupManager; private final CategoryManager categoryManager; + private SortChooser> sortChooser; NavPanel(ImageGalleryController controller) { this.controller = controller; @@ -75,34 +60,35 @@ abstract class NavPanel extends Tab { this.categoryManager = controller.getCategoryManager(); } + public ReadOnlyObjectProperty> comparatorProperty() { + return sortChooser.comparatorProperty(); + } + @FXML @NbBundle.Messages({"NavPanel.ascRadio.text=Ascending", - "NavPanel.descRadio.text=Descending", - "NavPanel.sortByBoxLabel.text=Sort By:"}) + "NavPanel.descRadio.text=Descending", + "NavPanel.sortByBoxLabel.text=Sort By:"}) void initialize() { assert borderPane != null : "fx:id=\"borderPane\" was not injected: check your FXML file 'NavPanel.fxml'."; assert toolBar != null : "fx:id=\"toolBar\" was not injected: check your FXML file 'NavPanel.fxml'."; - 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()); - sortByBox.getSelectionModel().select(getDefaultComparator()); - orderGroup.selectedToggleProperty().addListener(order -> sortGroups()); - sortByBox.getSelectionModel().selectedItemProperty().addListener(observable -> { + sortChooser = new SortChooser<>(GroupComparators.getValues()); + sortChooser.setComparator(getDefaultComparator()); + sortChooser.sortOrderProperty().addListener(order -> sortGroups()); + sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { sortGroups(); //only need to listen to changes in category if we are sorting by/ showing the uncategorized count - if (sortByBox.getSelectionModel().getSelectedItem() == GroupComparators.UNCATEGORIZED_COUNT) { + if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) { categoryManager.registerListener(NavPanel.this); } else { categoryManager.unregisterListener(NavPanel.this); } - }); - ascRadio.setText(Bundle.NavPanel_ascRadio_text()); - descRadio.setText(Bundle.NavPanel_descRadio_text()); - sortByBoxLabel.setText(Bundle.NavPanel_sortByBoxLabel_text()); + final SortChooser.ValueType valueType = newComparator == GroupComparators.ALPHABETICAL ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; + sortChooser.setValueType(valueType); + }); + toolBar.getItems().add(sortChooser); + //keep selection in sync with controller controller.viewState().addListener(observable -> { Optional.ofNullable(controller.viewState().get()) @@ -129,8 +115,8 @@ abstract class NavPanel extends Tab { */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) Comparator getComparator() { - Comparator comparator = sortByBox.getSelectionModel().getSelectedItem(); - return (orderGroup.getSelectedToggle() == ascRadio) + Comparator comparator = sortChooser.getComparator(); + return (sortChooser.getSortOrder() == SortOrder.ASCENDING) ? comparator : comparator.reversed(); } @@ -194,22 +180,6 @@ abstract class NavPanel extends Tab { return toolBar; } - ComboBox> getSortByBox() { - return sortByBox; - } - - RadioButton getAscRadio() { - return ascRadio; - } - - ToggleGroup getOrderGroup() { - return orderGroup; - } - - RadioButton getDescRadio() { - return descRadio; - } - ImageGalleryController getController() { return controller; } From 41af6434738ba323a91e5fd880bfd11fbcf128e4 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Feb 2016 16:47:26 -0500 Subject: [PATCH 11/67] add tag icons to context menus in IG --- .../actions/AddDrawableTagAction.java | 127 ------------- .../imagegallery/actions/AddTagAction.java | 173 ++++++++++++------ .../actions/CategorizeGroupAction.java | 8 +- .../imagegallery/actions/TagGroupAction.java | 10 +- .../actions/TagSelectedFilesAction.java | 6 +- .../datamodel/DrawableTagsManager.java | 35 +++- .../gui/drawableviews/DrawableTileBase.java | 7 +- .../gui/drawableviews/GroupPane.java | 8 +- 8 files changed, 161 insertions(+), 213 deletions(-) delete mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java deleted file mode 100644 index 92e9a979c2..0000000000 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-15 Basis Technology Corp. - * Contact: carrier sleuthkit 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 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 selectedFiles) { - new SwingWorker() { - - @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 contentTags = tagsManager.getContentTagsByContent(file); - Optional 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(); - } -} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java index b633c08ced..72600d2e5d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,65 +20,128 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.awt.Window; import java.util.Collection; +import java.util.List; +import java.util.Optional; 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.MenuItem; +import javafx.scene.image.ImageView; 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.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog; import org.sleuthkit.autopsy.actions.GetTagNameDialog; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; +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.TskCoreException; /** - * An abstract base class for actions that allow users to tag SleuthKit data - * 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. + * Instances of this Action allow users to apply tags to content. */ -abstract class AddTagAction { +public class AddTagAction extends Action { - protected static final String NO_COMMENT = ""; + private static final Logger LOGGER = Logger.getLogger(AddTagAction.class.getName()); - /** - * Template method to allow derived classes to provide a string for a menu - * item label. - */ - abstract protected String getActionDisplayName(); + private final ImageGalleryController controller; + private final Set selectedFileIDs; + private final TagName tagName; - /** - * Template method to allow derived classes to add the indicated tag and - * comment to one or more a SleuthKit data model objects. - */ - abstract protected void addTag(TagName tagName, String comment); + public AddTagAction(ImageGalleryController controller, TagName tagName, Set selectedFileIDs) { + super(tagName.getDisplayName()); + this.controller = controller; + this.selectedFileIDs = selectedFileIDs; + this.tagName = tagName; + setGraphic(controller.getTagsManager().getGraphic(tagName)); + setText(tagName.getDisplayName()); + setEventHandler(actionEvent -> addTagWithComment("")); + } - /** - * Template method to allow derived classes to add the indicated tag and - * comment to a list of one or more file IDs. - */ - abstract protected void addTagsToFiles(TagName tagName, String comment, Set selectedFiles); + static public Menu getTagMenu(ImageGalleryController controller) { + return new TagMenu(controller); + } + + 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 selectedFiles) { + new SwingWorker() { + + @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 contentTags = tagsManager.getContentTagsByContent(file); + Optional 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", "AddTagAction.menuItem.noTags=No tags", "AddTagAction.menuItem.newTag=New Tag...", - "AddTagAction.menuItem.tagAndComment=Tag and Comment..."}) - protected class TagMenu extends Menu { + "AddTagAction.menuItem.tagAndComment=Tag and Comment...", + "AddDrawableTagAction.displayName.plural=Tag Files", + "AddDrawableTagAction.displayName.singular=Tag File"}) + private static class TagMenu extends Menu { TagMenu(ImageGalleryController controller) { - super(getActionDisplayName()); + setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon())); + ObservableSet selectedFileIDs = controller.getSelectionModel().getSelected(); + setText(selectedFileIDs.size() > 1 + ? Bundle.AddDrawableTagAction_displayName_plural() + : Bundle.AddDrawableTagAction_displayName_singular()); // Create a "Quick Tag" sub-menu. Menu quickTagMenu = new Menu(Bundle.AddTagAction_menuItem_quickTag()); @@ -96,10 +159,8 @@ abstract class AddTagAction { quickTagMenu.getItems().add(empty); } else { for (final TagName tagName : tagNames) { - MenuItem tagNameItem = new MenuItem(tagName.getDisplayName()); - tagNameItem.setOnAction((ActionEvent t) -> { - addTag(tagName, NO_COMMENT); - }); + AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs); + MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction); quickTagMenu.getItems().add(tagNameItem); } } @@ -110,14 +171,13 @@ abstract class AddTagAction { * or select a tag name and adds a tag with the resulting name. */ MenuItem newTagMenuItem = new MenuItem(Bundle.AddTagAction_menuItem_newTag()); - newTagMenuItem.setOnAction((ActionEvent t) -> { - SwingUtilities.invokeLater(() -> { - TagName tagName = GetTagNameDialog.doDialog(getIGWindow()); - if (tagName != null) { - addTag(tagName, NO_COMMENT); - } - }); - }); + newTagMenuItem.setOnAction(actionEvent -> + SwingUtilities.invokeLater(() -> { + TagName tagName = GetTagNameDialog.doDialog(getIGWindow()); + if (tagName != null) { + new AddTagAction(controller, tagName, selectedFileIDs).handle(actionEvent); + } + })); quickTagMenu.getItems().add(newTagMenuItem); /* @@ -127,22 +187,17 @@ abstract class AddTagAction { * name. */ MenuItem tagAndCommentItem = new MenuItem(Bundle.AddTagAction_menuItem_tagAndComment()); - tagAndCommentItem.setOnAction((ActionEvent t) -> { - SwingUtilities.invokeLater(() -> { - GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow()); - if (null != tagNameAndComment) { - new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); - } - }); - }); + tagAndCommentItem.setOnAction(actionEvent -> + SwingUtilities.invokeLater(() -> { + GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow()); + if (null != tagNameAndComment) { + new AddTagAction(controller, tagNameAndComment.getTagName(), selectedFileIDs).addTagWithComment(tagNameAndComment.getComment()); + } + })); getItems().add(tagAndCommentItem); } - } - /** - * @return the Window containing the ImageGalleryTopComponent - */ static private Window getIGWindow() { TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID); return SwingUtilities.getWindowAncestor(etc); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index 00df7e1fc0..e7c7c1b1ef 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -19,19 +19,15 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.util.HashSet; -import org.controlsfx.control.action.Action; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; /** * */ -public class CategorizeGroupAction extends Action { +public class CategorizeGroupAction extends CategorizeAction { public CategorizeGroupAction(Category cat, ImageGalleryController controller) { - super(cat.getDisplayName(), actionEvent -> - new CategorizeAction(controller, cat, new HashSet<>(controller.viewState().get().getGroup().getFileIDs())) - .handle(actionEvent) - ); + super(controller, cat, new HashSet<>(controller.viewState().get().getGroup().getFileIDs())); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java index c53d796a60..a989952bca 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java @@ -19,21 +19,15 @@ package org.sleuthkit.autopsy.imagegallery.actions; 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.datamodel.TagName; /** * */ -public class TagGroupAction extends Action { +public class TagGroupAction extends AddTagAction { public TagGroupAction(final TagName tagName, ImageGalleryController controller) { - super(tagName.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> { - Set fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs()); - new AddDrawableTagAction(controller).addTagsToFiles(tagName, "", fileIdSet); - }); - setGraphic(controller.getTagsManager().getGraphic(tagName)); + super(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagSelectedFilesAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagSelectedFilesAction.java index 3aafc804a1..40c36ee2cc 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagSelectedFilesAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagSelectedFilesAction.java @@ -18,17 +18,15 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; -import org.controlsfx.control.action.Action; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.datamodel.TagName; /** * */ -public class TagSelectedFilesAction extends Action { +public class TagSelectedFilesAction extends AddTagAction { public TagSelectedFilesAction(final TagName tagName, ImageGalleryController controller) { - super(tagName.getDisplayName(), actionEvent -> new AddDrawableTagAction(controller).addTag(tagName, "")); - setGraphic(controller.getTagsManager().getGraphic(tagName)); + super(controller, tagName, controller.getSelectionModel().getSelected()); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index ea2f17f62e..9aa516a644 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -45,13 +45,24 @@ import org.sleuthkit.datamodel.TskCoreException; * Manages Tags, Tagging, and the relationship between Categories and Tags in * 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 { private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName()); 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 BOOKMARK_IMAGE; + + public static String getFollowUpText() { + return FOLLOW_UP; + } + + public static String getBookmarkText() { + return BOOKMARK; + } final private Object autopsyTagsManagerLock = new Object(); private TagsManager autopsyTagsManager; @@ -70,6 +81,7 @@ public class DrawableTagsManager { * The tag name corresponding to the "built-in" tag "Follow Up" */ private TagName followUpTagName; + private TagName bookmarkTagName; public DrawableTagsManager(TagsManager 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 * @@ -241,9 +262,11 @@ public class DrawableTagsManager { try { if (tagname.equals(getFollowUpTagName())) { return new ImageView(getFollowUpImage()); + } else if (tagname.equals(getBookmarkTagName())) { + return new ImageView(getBookmarkImage()); } } 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); } @@ -254,4 +277,12 @@ public class DrawableTagsManager { } 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; + } + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index a353d97cc0..65165f7276 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -63,7 +63,7 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; 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.DeleteFollowUpTagAction; import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction; @@ -183,8 +183,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { final ArrayList menuItems = new ArrayList<>(); menuItems.add(CategorizeAction.getCategoriesMenu(getController())); - - menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu()); + menuItems.add(AddTagAction.getTagMenu(getController())); final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles()); extractMenuItem.setOnAction(actionEvent -> { @@ -240,7 +239,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { if (followUpToggle.isSelected() == true) { try { selectionModel.clearAndSelect(file.getId()); - new AddDrawableTagAction(getController()).addTag(getController().getTagsManager().getFollowUpTagName(), ""); + new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 6cb63fb84c..5727c8d110 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -112,7 +112,7 @@ import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; 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.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeSelectedFilesAction; @@ -832,10 +832,12 @@ public class GroupPane extends BorderPane { private ContextMenu buildContextMenu() { ArrayList menuItems = new ArrayList<>(); + menuItems.add(CategorizeAction.getCategoriesMenu(controller)); - menuItems.add(new AddDrawableTagAction(controller).getPopupMenu()); - + menuItems.add(AddTagAction.getTagMenu(controller)); + + Collection menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); for (ContextMenuActionsProvider provider : menuProviders) { From 1465e1a529f6690dbeee73401d26e7cbd8635b72 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 12 Feb 2016 14:25:58 -0500 Subject: [PATCH 12/67] cleanup CategorizeAction, StatusBar, and ImageGalleryController, especially realted to background(db) tasks --- .../imagegallery/ImageGalleryController.java | 153 +++++++++--------- .../actions/CategorizeAction.java | 2 +- .../autopsy/imagegallery/gui/StatusBar.fxml | 43 +++-- .../autopsy/imagegallery/gui/StatusBar.java | 47 +----- 4 files changed, 109 insertions(+), 136 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 7fb7f9032f..1ac88ee221 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -128,8 +128,6 @@ public final class ImageGalleryController implements Executor { */ private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false); - private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0); - private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @@ -154,7 +152,6 @@ public final class ImageGalleryController implements Executor { private Node infoOverlay; private SleuthkitCase sleuthKitCase; -// private NavPanel navPanel; public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); @@ -176,7 +173,7 @@ public final class ImageGalleryController implements Executor { return historyManager.currentState(); } - public synchronized FileIDSelectionModel getSelectionModel() { + public FileIDSelectionModel getSelectionModel() { return selectionModel; } @@ -188,12 +185,16 @@ public final class ImageGalleryController implements Executor { return db; } - synchronized public void setListeningEnabled(boolean enabled) { - listeningEnabled.set(enabled); + public void setListeningEnabled(boolean enabled) { + synchronized (listeningEnabled) { + listeningEnabled.set(enabled); + } } - synchronized boolean isListeningEnabled() { - return listeningEnabled.get(); + boolean isListeningEnabled() { + synchronized (listeningEnabled) { + return listeningEnabled.get(); + } } @ThreadConfined(type = ThreadConfined.ThreadType.ANY) @@ -249,12 +250,14 @@ public final class ImageGalleryController implements Executor { checkForGroups(); }); - IngestManager.getInstance().addIngestModuleEventListener((PropertyChangeEvent evt) -> { - Platform.runLater(this::updateRegroupDisabled); - }); - IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> { - Platform.runLater(this::updateRegroupDisabled); - }); + IngestManager ingestManager = IngestManager.getInstance(); + PropertyChangeListener ingestEventHandler = + propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled); + + ingestManager.addIngestModuleEventListener(ingestEventHandler); + ingestManager.addIngestJobEventListener(ingestEventHandler); + + queueSizeProperty.addListener(obs -> this.updateRegroupDisabled()); } public ReadOnlyBooleanProperty getCanAdvance() { @@ -281,8 +284,9 @@ public final class ImageGalleryController implements Executor { return historyManager.retreat(); } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void updateRegroupDisabled() { - regroupDisabled.set(getFileUpdateQueueSizeProperty().get() > 0 || IngestManager.getInstance().isIngestRunning()); + regroupDisabled.set((queueSizeProperty.get() > 0) || IngestManager.getInstance().isIngestRunning()); } /** @@ -313,7 +317,7 @@ public final class ImageGalleryController implements Executor { new ProgressIndicator())); } - } else if (getFileUpdateQueueSizeProperty().get() > 0) { + } else if (queueSizeProperty.get() > 0) { replaceNotification(fullUIStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), new ProgressIndicator())); @@ -358,20 +362,14 @@ public final class ImageGalleryController implements Executor { } } - private void restartWorker() { - if (dbWorkerThread != null) { + synchronized private DBWorkerThread restartWorker() { + if (dbWorkerThread == null) { + dbWorkerThread = new DBWorkerThread(this); + dbWorkerThread.start(); + } else { // Keep using the same worker thread if one exists - return; } - dbWorkerThread = new 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(); + return dbWorkerThread; } /** @@ -412,15 +410,16 @@ public final class ImageGalleryController implements Executor { setListeningEnabled(false); ThumbnailCache.getDefault().clearCache(); historyManager.clear(); + groupManager.clear(); tagsManager.clearFollowUpTagName(); tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); - dbWorkerThread.cancelAllTasks(); + dbWorkerThread.cancel(); dbWorkerThread = null; - restartWorker(); + dbWorkerThread = restartWorker(); Toolbar.getDefault(this).reset(); - groupManager.clear(); + if (db != null) { db.closeDBCon(); } @@ -432,11 +431,9 @@ public final class ImageGalleryController implements Executor { * * @param innerTask */ - public void queueDBWorkerTask(InnerTask innerTask) { - - // @@@ We could make a lock for the worker thread + public synchronized void queueDBWorkerTask(BackgroundTask innerTask) { if (dbWorkerThread == null) { - restartWorker(); + dbWorkerThread = restartWorker(); } dbWorkerThread.addTask(innerTask); } @@ -456,10 +453,6 @@ public final class ImageGalleryController implements Executor { Platform.runLater(this::checkForGroups); } - public ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() { - return queueSizeProperty.getReadOnlyProperty(); - } - public ReadOnlyDoubleProperty regroupProgress() { return groupManager.regroupProgress(); } @@ -497,29 +490,43 @@ public final class ImageGalleryController implements Executor { return undoManager; } - // @@@ REVIEW IF THIS SHOLD BE STATIC... - //TODO: concept seems like the controller deal with how much work to do at a given time + public ReadOnlyIntegerProperty getDBTasksQueueSizeProperty() { + 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.) - 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 private volatile boolean cancelled = false; // list of tasks to run - private final BlockingQueue workQueue = new LinkedBlockingQueue<>(); + private final BlockingQueue workQueue = new LinkedBlockingQueue<>(); /** * Cancel all of the queued up tasks and the currently scheduled task. * Note that after you cancel, you cannot submit new jobs to this * thread. */ - public void cancelAllTasks() { + @Override + public boolean cancel() { cancelled = true; - for (InnerTask it : workQueue) { + for (BackgroundTask it : workQueue) { it.cancel(); } workQueue.clear(); - queueSizeProperty.set(workQueue.size()); + int size = workQueue.size(); + Platform.runLater(() -> controller.queueSizeProperty.set(size)); + return true; } /** @@ -527,11 +534,10 @@ public final class ImageGalleryController implements Executor { * * @param it */ - public void addTask(InnerTask it) { + public void addTask(BackgroundTask it) { workQueue.add(it); - Platform.runLater(() -> { - queueSizeProperty.set(workQueue.size()); - }); + int size = workQueue.size(); + Platform.runLater(() -> controller.queueSizeProperty.set(size)); } @Override @@ -539,19 +545,17 @@ public final class ImageGalleryController implements Executor { // nearly infinite loop waiting for tasks while (true) { - if (cancelled) { + if (cancelled || isInterrupted()) { return; } try { - InnerTask it = workQueue.take(); + BackgroundTask it = workQueue.take(); if (it.isCancelled() == false) { it.run(); } - - Platform.runLater(() -> { - queueSizeProperty.set(workQueue.size()); - }); + int size = workQueue.size(); + Platform.runLater(() -> controller.queueSizeProperty.set(size)); } catch (InterruptedException ex) { LOGGER.log(Level.SEVERE, "Failed to run DB worker thread", ex); //NON-NLS @@ -569,7 +573,14 @@ public final class ImageGalleryController implements Executor { */ @NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress", "ImageGalleryController.InnerTask.message.name=status"}) - static public abstract class InnerTask implements Runnable, Cancellable { + static public abstract class BackgroundTask implements Runnable, Cancellable { + + private final SimpleObjectProperty 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() { return progress.get(); @@ -586,9 +597,6 @@ public final class ImageGalleryController implements Executor { public final void updateMessage(String Status) { this.message.set(Status); } - private final SimpleObjectProperty 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()); public SimpleDoubleProperty progressProperty() { return progress; @@ -602,24 +610,21 @@ public final class ImageGalleryController implements Executor { return state.get(); } - protected void updateState(Worker.State newState) { - state.set(newState); - } - public ReadOnlyObjectProperty stateProperty() { return new ReadOnlyObjectWrapper<>(state.get()); } - protected InnerTask() { - } - @Override - synchronized public boolean cancel() { + public synchronized boolean cancel() { updateState(Worker.State.CANCELLED); return true; } - synchronized protected boolean isCancelled() { + protected void updateState(Worker.State newState) { + state.set(newState); + } + + protected synchronized boolean isCancelled() { return getState() == Worker.State.CANCELLED; } } @@ -627,7 +632,7 @@ public final class ImageGalleryController implements Executor { /** * 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 DrawableDB taskDB; @@ -704,7 +709,7 @@ public final class ImageGalleryController implements Executor { @NbBundle.Messages({"BulkTask.committingDb.status=commiting image/video database", "BulkTask.stopCopy.status=Stopping copy to drawable db task.", "BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."}) - abstract static private class BulkTransferTask extends InnerTask { + abstract static private class BulkTransferTask extends BackgroundTask { static private final String FILE_EXTENSION_CLAUSE = "(name LIKE '%." //NON-NLS @@ -805,10 +810,11 @@ public final class ImageGalleryController implements Executor { * 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 { - private static final Logger LOGGER = Logger.getLogger(CopyAnalyzedFiles.class.getName()); - CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { super(controller, taskDB, tskCase); } @@ -866,6 +872,7 @@ public final class ImageGalleryController implements Executor { * TODO: create methods to simplify progress value/text updates to both * netbeans and ImageGallery progress/status */ + @NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"}) static private class PrePopulateDataSourceFiles extends BulkTransferTask { private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName()); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index ab59df29e4..77d3f2c0f9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -112,7 +112,7 @@ public class CategorizeAction extends Action { @NbBundle.Messages({"# {0} - fileID number", "CategorizeTask.errorUnable.msg=Unable to categorize {0}.", "CategorizeTask.errorUnable.title=Categorizing Error"}) - private class CategorizeTask extends ImageGalleryController.InnerTask { + private class CategorizeTask extends ImageGalleryController.BackgroundTask { private final Set fileIDs; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.fxml index 03d26395ce..b02e2e2496 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.fxml @@ -1,16 +1,20 @@ - - - - - + + + + + + + + + - + - + @@ -18,7 +22,10 @@ + + + + @@ -27,10 +34,13 @@ - @@ -42,19 +52,6 @@ -
- - - - - -