simplify locking in DrawableTagsManager.java by making the backing tagsManager final

This commit is contained in:
millmanorama 2018-09-03 17:31:14 +02:00
parent 31903d04d8
commit a37b8dc743
5 changed files with 261 additions and 332 deletions

View File

@ -60,6 +60,7 @@ import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.Cancellable; import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.casemodule.Case.CaseType;
@ -123,7 +124,7 @@ public final class ImageGalleryController {
private final GroupManager groupManager = new GroupManager(this); private final GroupManager groupManager = new GroupManager(this);
private final HashSetManager hashSetManager = new HashSetManager(); private final HashSetManager hashSetManager = new HashSetManager();
private final CategoryManager categoryManager = new CategoryManager(this); private final CategoryManager categoryManager = new CategoryManager(this);
private final DrawableTagsManager tagsManager = new DrawableTagsManager(null); private DrawableTagsManager tagsManager;
private Runnable showTree; private Runnable showTree;
private Toolbar toolbar; private Toolbar toolbar;
@ -144,7 +145,11 @@ public final class ImageGalleryController {
public static synchronized ImageGalleryController getDefault() { public static synchronized ImageGalleryController getDefault() {
if (instance == null) { if (instance == null) {
instance = new ImageGalleryController(); try {
instance = new ImageGalleryController();
} catch (NoClassDefFoundError error) {
Exceptions.printStackTrace(error);
}
} }
return instance; return instance;
} }
@ -232,12 +237,7 @@ public final class ImageGalleryController {
} }
}); });
groupManager.getAnalyzedGroups().addListener((Observable o) -> { groupManager.getAnalyzedGroups().addListener((Observable o) -> checkForGroups());
//analyzed groups is confined to JFX thread
if (Case.isCaseOpen()) {
checkForGroups();
}
});
viewState().addListener((Observable observable) -> { viewState().addListener((Observable observable) -> {
//when the viewed group changes, clear the selection and the undo/redo history //when the viewed group changes, clear the selection and the undo/redo history
@ -291,7 +291,6 @@ public final class ImageGalleryController {
* GroupManager and remove blocking progress spinners if there are. If there * GroupManager and remove blocking progress spinners if there are. If there
* aren't, add a blocking progress spinner with appropriate message. * aren't, add a blocking progress spinner with appropriate message.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " @NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
+ " No groups will be available until ingest is finished and listening is re-enabled.", + " No groups will be available until ingest is finished and listening is re-enabled.",
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", "ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
@ -303,45 +302,46 @@ public final class ImageGalleryController {
+ " the current Group By setting resulted in no groups, " + " the current Group By setting resulted in no groups, "
+ "or no groups are fully analyzed but ingest is not running."}) + "or no groups are fully analyzed but ingest is not running."})
synchronized private void checkForGroups() { synchronized private void checkForGroups() {
if (groupManager.getAnalyzedGroups().isEmpty()) { if (Case.isCaseOpen()) {
if (IngestManager.getInstance().isIngestRunning()) { if (groupManager.getAnalyzedGroups().isEmpty()) {
if (listeningEnabled.not().get()) { if (IngestManager.getInstance().isIngestRunning()) {
replaceNotification(fullUIStackPane, if (listeningEnabled.get()) {
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); replaceNotification(centralStackPane,
} else { new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(),
replaceNotification(fullUIStackPane, new ProgressIndicator()));
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), } else {
new ProgressIndicator())); replaceNotification(fullUIStackPane,
} new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()));
} else if (dbTaskQueueSize.get() > 0) {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
new ProgressIndicator()));
} else if (db != null) {
try {
if (db.countAllFiles() <= 0) {
// there are no files in db
if (listeningEnabled.not().get()) {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
} else {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
}
} }
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); } else if (dbTaskQueueSize.get() > 0) {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
new ProgressIndicator()));
} else if (db != null) {
try {
if (db.countAllFiles() <= 0) {
// there are no files in db
if (listeningEnabled.get()) {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
} else {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
}
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error counting files in drawable db.", ex);
}
} else if (false == groupManager.isRegrouping()) {
replaceNotification(centralStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
} }
} else if (!groupManager.isRegrouping()) { } else {
replaceNotification(centralStackPane, Platform.runLater(this::clearNotification);
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
} }
} else {
clearNotification();
} }
} }
@ -357,22 +357,25 @@ public final class ImageGalleryController {
} }
} }
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void replaceNotification(StackPane stackPane, Node newNode) { private void replaceNotification(StackPane stackPane, Node newNode) {
clearNotification(); Platform.runLater(() -> {
clearNotification();
infoOverlay = new StackPane(infoOverLayBackground, newNode); infoOverlay = new StackPane(infoOverLayBackground, newNode);
if (stackPane != null) { if (stackPane != null) {
stackPane.getChildren().add(infoOverlay); stackPane.getChildren().add(infoOverlay);
} }
});
} }
/** /**
* configure the controller for a specific case. * configure the controller for a specific case.
* *
* @param theNewCase the case to configure the controller for * @param theNewCase the case to configure the controller for
*
* @throws org.sleuthkit.datamodel.TskCoreException
*/ */
public synchronized void setCase(Case theNewCase) { public synchronized void setCase(Case theNewCase) throws TskCoreException {
if (null == theNewCase) { if (null == theNewCase) {
reset(); reset();
} else { } else {
@ -385,10 +388,15 @@ public final class ImageGalleryController {
// if we add this line icons are made as files are analyzed rather than on demand. // if we add this line icons are made as files are analyzed rather than on demand.
// db.addUpdatedFileListener(IconCache.getDefault()); // db.addUpdatedFileListener(IconCache.getDefault());
historyManager.clear(); historyManager.clear();
groupManager.setDB(db); groupManager.reset();
hashSetManager.setDb(db); hashSetManager.setDb(db);
categoryManager.setDb(db); categoryManager.setDb(db);
tagsManager.setAutopsyTagsManager(theNewCase.getServices().getTagsManager());
if (tagsManager != null) {
tagsManager.unregisterListener(groupManager);
tagsManager.unregisterListener(categoryManager);
}
tagsManager = new DrawableTagsManager(theNewCase.getServices().getTagsManager());
tagsManager.registerListener(groupManager); tagsManager.registerListener(groupManager);
tagsManager.registerListener(categoryManager); tagsManager.registerListener(categoryManager);
shutDownDBExecutor(); shutDownDBExecutor();
@ -416,10 +424,11 @@ public final class ImageGalleryController {
setListeningEnabled(false); setListeningEnabled(false);
ThumbnailCache.getDefault().clearCache(); ThumbnailCache.getDefault().clearCache();
historyManager.clear(); historyManager.clear();
groupManager.clear(); groupManager.reset();
tagsManager.clearFollowUpTagName();
tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(groupManager);
tagsManager.unregisterListener(categoryManager); tagsManager.unregisterListener(categoryManager);
tagsManager = null;
shutDownDBExecutor(); shutDownDBExecutor();
if (toolbar != null) { if (toolbar != null) {
@ -854,6 +863,9 @@ public final class ImageGalleryController {
taskDB.commitTransaction(drawableDbTransaction, true); taskDB.commitTransaction(drawableDbTransaction, true);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
if (null != drawableDbTransaction) {
taskDB.rollbackTransaction(drawableDbTransaction);
}
if (null != caseDbTransaction) { if (null != caseDbTransaction) {
try { try {
caseDbTransaction.rollback(); caseDbTransaction.rollback();
@ -861,9 +873,6 @@ public final class ImageGalleryController {
logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS
} }
} }
if (null != drawableDbTransaction) {
taskDB.rollbackTransaction(drawableDbTransaction);
}
progressHandle.progress(Bundle.BulkTask_stopCopy_status()); 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 logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
@ -1065,8 +1074,13 @@ public final class ImageGalleryController {
//close window, reset everything //close window, reset everything
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent); SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
reset(); reset();
} else { // a new case has been opened } else {
setCase(newCase); //connect db, groupmanager, start worker thread try {
// a new case has been opened
setCase(newCase); //connect db, groupmanager, start worker thread
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex);
}
} }
break; break;
case DATA_SOURCE_ADDED: case DATA_SOURCE_ADDED:

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-16 Basis Technology Corp. * Copyright 2013-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,25 +18,24 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.datamodel; package org.sleuthkit.autopsy.imagegallery.datamodel;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
@ -44,39 +43,40 @@ import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Manages Tags, Tagging, and the relationship between Categories and Tags in * Manages Tags, Tagging, and the relationship between Categories and Tags in
* the autopsy Db. Delegates some work to the backing {@link TagsManager}. * the autopsy Db. Delegates some work to the backing autopsy TagsManager.
*/ */
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up", @NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up",
"DrawableTagsManager.bookMark=Bookmark"}) "DrawableTagsManager.bookMark=Bookmark"})
public class DrawableTagsManager { public final class DrawableTagsManager {
private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName()); private static final Logger logger = Logger.getLogger(DrawableTagsManager.class.getName());
private static Image FOLLOW_UP_IMAGE; private static final Image FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png");
private static Image BOOKMARK_IMAGE; private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
final private Object autopsyTagsManagerLock = new Object(); private final TagsManager autopsyTagsManager;
private TagsManager autopsyTagsManager;
/** The tag name corresponding to the "built-in" tag "Follow Up" */
private final TagName followUpTagName;
private final TagName bookmarkTagName;
/** /**
* Used to distribute {@link TagsChangeEvent}s * Used to distribute {@link TagsChangeEvent}s
*/ */
private final EventBus tagsEventBus = new AsyncEventBus( private final EventBus tagsEventBus
Executors.newSingleThreadExecutor( = new AsyncEventBus(
new BasicThreadFactory.Builder().namingPattern("Tags Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS Executors.newSingleThreadExecutor(
LOGGER.log(Level.SEVERE, "uncaught exception in event bus handler", e); //NON-NLS new BasicThreadFactory.Builder()
}).build() .namingPattern("Tags Event Bus")//NON-NLS
)); .uncaughtExceptionHandler((Thread t, Throwable e) -> {
logger.log(Level.SEVERE, "Uncaught exception in DrawableTagsManager event bus handler.", e); //NON-NLS
})
.build()));
/** public DrawableTagsManager(TagsManager autopsyTagsManager) throws TskCoreException {
* The tag name corresponding to the "built-in" tag "Follow Up"
*/
private TagName followUpTagName;
private TagName bookmarkTagName;
public DrawableTagsManager(TagsManager autopsyTagsManager) {
this.autopsyTagsManager = autopsyTagsManager; this.autopsyTagsManager = autopsyTagsManager;
followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp"));
bookmarkTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.bookMark"));
} }
/** /**
@ -106,72 +106,37 @@ public class DrawableTagsManager {
} }
/** /**
* assign a new TagsManager to back this one, ie when the current case * Get the follow up TagName.
* changes
* *
* @param autopsyTagsManager * @return The follow up TagName.
*/ */
public void setAutopsyTagsManager(TagsManager autopsyTagsManager) { public TagName getFollowUpTagName() {
synchronized (autopsyTagsManagerLock) { return followUpTagName;
this.autopsyTagsManager = autopsyTagsManager;
clearFollowUpTagName();
}
} }
/** /**
* Use when closing a case to make sure everything is re-initialized in the * Get the bookmark TagName.
* next case. *
* @return The bookmark TagName.
*/ */
public void clearFollowUpTagName() { private TagName getBookmarkTagName() throws TskCoreException {
synchronized (autopsyTagsManagerLock) { return bookmarkTagName;
followUpTagName = null;
}
} }
/** /**
* get the (cached) follow up TagName * Get all the TagNames that are not categories
* *
* @return * @return All the TagNames that are not categories, in alphabetical order
*
* @throws TskCoreException
*/
public TagName getFollowUpTagName() throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
if (Objects.isNull(followUpTagName)) {
followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp"));
}
return followUpTagName;
}
}
private Object getBookmarkTagName() throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
if (Objects.isNull(bookmarkTagName)) {
bookmarkTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.bookMark"));
}
return bookmarkTagName;
}
}
/**
* get all the TagNames that are not categories
*
* @return all the TagNames that are not categories, in alphabetical order
* by displayName, or, an empty set if there was an exception * by displayName, or, an empty set if there was an exception
* looking them up from the db. * looking them up from the db.
*/ */
@Nonnull
public List<TagName> getNonCategoryTagNames() { public List<TagName> getNonCategoryTagNames() {
synchronized (autopsyTagsManagerLock) { try {
try { return autopsyTagsManager.getAllTagNames().stream()
return autopsyTagsManager.getAllTagNames().stream() .filter(CategoryManager::isNotCategoryTagName)
.filter(CategoryManager::isNotCategoryTagName) .distinct().sorted()
.distinct().sorted() .collect(Collectors.toList());
.collect(Collectors.toList()); } catch (TskCoreException tskCoreException) {
} catch (TskCoreException | IllegalStateException ex) {
LOGGER.log(Level.WARNING, "couldn't access case", ex); //NON-NLS
}
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@ -187,9 +152,7 @@ public class DrawableTagsManager {
* @throws TskCoreException if there was an error reading from the db * @throws TskCoreException if there was an error reading from the db
*/ */
public List<ContentTag> getContentTags(Content content) throws TskCoreException { public List<ContentTag> getContentTags(Content content) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { return autopsyTagsManager.getContentTagsByContent(content);
return autopsyTagsManager.getContentTagsByContent(content);
}
} }
/** /**
@ -207,25 +170,23 @@ public class DrawableTagsManager {
} }
public TagName getTagName(String displayName) throws TskCoreException { public TagName getTagName(String displayName) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { try {
TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
if (returnTagName != null) {
return returnTagName;
}
try { try {
TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); return autopsyTagsManager.addTagName(displayName);
} catch (TagsManager.TagNameAlreadyExistsException ex) {
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
if (returnTagName != null) { if (returnTagName != null) {
return returnTagName; return returnTagName;
} }
try { throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
return autopsyTagsManager.addTagName(displayName);
} catch (TagsManager.TagNameAlreadyExistsException ex) {
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
if (returnTagName != null) {
return returnTagName;
}
throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
}
} catch (NullPointerException | IllegalStateException ex) {
LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS
throw new TskCoreException("Case was closed out from underneath", ex);
} }
} catch (NullPointerException | IllegalStateException ex) {
logger.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS
throw new TskCoreException("Case was closed out from underneath", ex);
} }
} }
@ -233,65 +194,41 @@ public class DrawableTagsManager {
try { try {
return getTagName(cat.getDisplayName()); return getTagName(cat.getDisplayName());
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting tag for Category: " + cat.getDisplayName(), ex);
return null; return null;
} }
} }
public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException { public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
}
} }
public List<ContentTag> getContentTagsByTagName(TagName t) throws TskCoreException { public List<ContentTag> getContentTagsByTagName(TagName t) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { return autopsyTagsManager.getContentTagsByTagName(t);
return autopsyTagsManager.getContentTagsByTagName(t);
}
} }
public List<TagName> getAllTagNames() throws TskCoreException { public List<TagName> getAllTagNames() throws TskCoreException {
synchronized (autopsyTagsManagerLock) { return autopsyTagsManager.getAllTagNames();
return autopsyTagsManager.getAllTagNames();
}
} }
public List<TagName> getTagNamesInUse() throws TskCoreException { public List<TagName> getTagNamesInUse() throws TskCoreException {
synchronized (autopsyTagsManagerLock) { return autopsyTagsManager.getTagNamesInUse();
return autopsyTagsManager.getTagNamesInUse();
}
} }
public void deleteContentTag(ContentTag ct) throws TskCoreException { public void deleteContentTag(ContentTag ct) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { autopsyTagsManager.deleteContentTag(ct);
autopsyTagsManager.deleteContentTag(ct);
}
} }
public Node getGraphic(TagName tagname) { public Node getGraphic(TagName tagname) {
try { try {
if (tagname.equals(getFollowUpTagName())) { if (tagname.equals(getFollowUpTagName())) {
return new ImageView(getFollowUpImage()); return new ImageView(FOLLOW_UP_IMAGE);
} else if (tagname.equals(getBookmarkTagName())) { } else if (tagname.equals(getBookmarkTagName())) {
return new ImageView(getBookmarkImage()); return new ImageView(BOOKMARK_IMAGE);
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex); logger.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex);
} }
return DrawableAttribute.TAGS.getGraphicForValue(tagname); return DrawableAttribute.TAGS.getGraphicForValue(tagname);
} }
synchronized private static Image getFollowUpImage() {
if (FOLLOW_UP_IMAGE == null) {
FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png");
}
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;
}
} }

View File

@ -231,7 +231,7 @@ public class GroupManager {
} }
} }
synchronized public void clear() { synchronized public void reset() {
if (groupByTask != null) { if (groupByTask != null) {
groupByTask.cancel(true); groupByTask.cancel(true);

View File

@ -188,9 +188,9 @@ public abstract class DrawableTileBase extends DrawableUIBase {
menuItems.add(CategorizeAction.getCategoriesMenu(getController())); menuItems.add(CategorizeAction.getCategoriesMenu(getController()));
menuItems.add(AddTagAction.getTagMenu(getController())); menuItems.add(AddTagAction.getTagMenu(getController()));
final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
if(selectedFilesList.size() == 1) { if (selectedFilesList.size() == 1) {
menuItems.add(DeleteTagAction.getTagMenu(getController())); menuItems.add(DeleteTagAction.getTagMenu(getController()));
} }
@ -243,32 +243,24 @@ public abstract class DrawableTileBase extends DrawableUIBase {
protected abstract String getTextForLabel(); protected abstract String getTextForLabel();
protected void initialize() { protected void initialize() {
followUpToggle.setOnAction(actionEvent -> { followUpToggle.setOnAction(
getFile().ifPresent(file -> { actionEvent -> getFile().ifPresent(
if (followUpToggle.isSelected() == true) { file -> {
try { if (followUpToggle.isSelected() == true) {
selectionModel.clearAndSelect(file.getId()); selectionModel.clearAndSelect(file.getId());
new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent); new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
} catch (TskCoreException ex) { } else {
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS new DeleteFollowUpTagAction(getController(), file).handle(actionEvent);
} }
} else { })
new DeleteFollowUpTagAction(getController(), file).handle(actionEvent); );
}
});
});
} }
protected boolean hasFollowUp() { protected boolean hasFollowUp() {
if (getFileID().isPresent()) { if (getFileID().isPresent()) {
try { TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS
TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); return DrawableAttribute.TAGS.getValue(getFile().get()).stream()
return DrawableAttribute.TAGS.getValue(getFile().get()).stream() .anyMatch(followUpTagName::equals);
.anyMatch(followUpTagName::equals);
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed to get follow up tag name ", ex); //NON-NLS
return true;
}
} else { } else {
return false; return false;
} }
@ -342,18 +334,14 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@Override @Override
public void handleTagAdded(ContentTagAddedEvent evt) { public void handleTagAdded(ContentTagAddedEvent evt) {
getFileID().ifPresent(fileID -> { getFileID().ifPresent(fileID -> {
try { final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); final ContentTag addedTag = evt.getAddedTag();
final ContentTag addedTag = evt.getAddedTag(); if (fileID == addedTag.getContent().getId()
if (fileID == addedTag.getContent().getId() && addedTag.getName().equals(followUpTagName)) {
&& addedTag.getName().equals(followUpTagName)) { Platform.runLater(() -> {
Platform.runLater(() -> { followUpImageView.setImage(followUpIcon);
followUpImageView.setImage(followUpIcon); followUpToggle.setSelected(true);
followUpToggle.setSelected(true); });
});
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS
} }
}); });
} }
@ -362,15 +350,11 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@Override @Override
public void handleTagDeleted(ContentTagDeletedEvent evt) { public void handleTagDeleted(ContentTagDeletedEvent evt) {
getFileID().ifPresent(fileID -> { getFileID().ifPresent(fileID -> {
try { final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); if (fileID == deletedTagInfo.getContentID()
if (fileID == deletedTagInfo.getContentID() && deletedTagInfo.getName().equals(followUpTagName)) {
&& deletedTagInfo.getName().equals(followUpTagName)) { updateFollowUpIcon();
updateFollowUpIcon();
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS
} }
}); });
} }

View File

@ -144,39 +144,39 @@ import org.sleuthkit.datamodel.TskCoreException;
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview * https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
*/ */
public class GroupPane extends BorderPane { public class GroupPane extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName());
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2); private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2); private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE); private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR)) new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
); );
private final FileIDSelectionModel selectionModel; private final FileIDSelectionModel selectionModel;
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5,
KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5);
private final Back backAction; private final Back backAction;
private final Forward forwardAction; private final Forward forwardAction;
@FXML @FXML
private Button undoButton; private Button undoButton;
@FXML @FXML
private Button redoButton; private Button redoButton;
@FXML @FXML
private SplitMenuButton catSelectedSplitMenu; private SplitMenuButton catSelectedSplitMenu;
@FXML @FXML
private SplitMenuButton tagSelectedSplitMenu; private SplitMenuButton tagSelectedSplitMenu;
@FXML @FXML
private ToolBar headerToolBar; private ToolBar headerToolBar;
@FXML @FXML
private ToggleButton cat0Toggle; private ToggleButton cat0Toggle;
@FXML @FXML
@ -189,30 +189,30 @@ public class GroupPane extends BorderPane {
private ToggleButton cat4Toggle; private ToggleButton cat4Toggle;
@FXML @FXML
private ToggleButton cat5Toggle; private ToggleButton cat5Toggle;
@FXML @FXML
private SegmentedButton segButton; private SegmentedButton segButton;
private SlideShowView slideShowPane; private SlideShowView slideShowPane;
@FXML @FXML
private ToggleButton slideShowToggle; private ToggleButton slideShowToggle;
@FXML @FXML
private GridView<Long> gridView; private GridView<Long> gridView;
@FXML @FXML
private ToggleButton tileToggle; private ToggleButton tileToggle;
@FXML @FXML
private Button nextButton; private Button nextButton;
@FXML @FXML
private Button backButton; private Button backButton;
@FXML @FXML
private Button forwardButton; private Button forwardButton;
@FXML @FXML
private Label groupLabel; private Label groupLabel;
@FXML @FXML
@ -223,24 +223,24 @@ public class GroupPane extends BorderPane {
private Label catContainerLabel; private Label catContainerLabel;
@FXML @FXML
private Label catHeadingLabel; private Label catHeadingLabel;
@FXML @FXML
private HBox catSegmentedContainer; private HBox catSegmentedContainer;
@FXML @FXML
private HBox catSplitMenuContainer; private HBox catSplitMenuContainer;
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
private final NextUnseenGroup nextGroupAction; private final NextUnseenGroup nextGroupAction;
private final ImageGalleryController controller; private final ImageGalleryController controller;
private ContextMenu contextMenu; private ContextMenu contextMenu;
private Integer selectionAnchorIndex; private Integer selectionAnchorIndex;
private final UndoAction undoAction; private final UndoAction undoAction;
private final RedoAction redoAction; private final RedoAction redoAction;
GroupViewMode getGroupViewMode() { GroupViewMode getGroupViewMode() {
return groupViewMode.get(); return groupViewMode.get();
} }
@ -263,7 +263,7 @@ public class GroupPane extends BorderPane {
*/ */
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private final Map<Long, DrawableCell> cellMap = new HashMap<>(); private final Map<Long, DrawableCell> cellMap = new HashMap<>();
private final InvalidationListener filesSyncListener = (observable) -> { private final InvalidationListener filesSyncListener = (observable) -> {
final String header = getHeaderString(); final String header = getHeaderString();
final List<Long> fileIds = getGroup().getFileIDs(); final List<Long> fileIds = getGroup().getFileIDs();
@ -273,7 +273,7 @@ public class GroupPane extends BorderPane {
groupLabel.setText(header); groupLabel.setText(header);
}); });
}; };
public GroupPane(ImageGalleryController controller) { public GroupPane(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
this.selectionModel = controller.getSelectionModel(); this.selectionModel = controller.getSelectionModel();
@ -282,10 +282,10 @@ public class GroupPane extends BorderPane {
forwardAction = new Forward(controller); forwardAction = new Forward(controller);
undoAction = new UndoAction(controller); undoAction = new UndoAction(controller);
redoAction = new RedoAction(controller); redoAction = new RedoAction(controller);
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
public void activateSlideShowViewer(Long slideShowFileID) { public void activateSlideShowViewer(Long slideShowFileID) {
groupViewMode.set(GroupViewMode.SLIDE_SHOW); groupViewMode.set(GroupViewMode.SLIDE_SHOW);
@ -301,16 +301,16 @@ public class GroupPane extends BorderPane {
} else { } else {
slideShowPane.setFile(slideShowFileID); slideShowPane.setFile(slideShowFileID);
} }
setCenter(slideShowPane); setCenter(slideShowPane);
slideShowPane.requestFocus(); slideShowPane.requestFocus();
} }
void syncCatToggle(DrawableFile file) { void syncCatToggle(DrawableFile file) {
getToggleForCategory(file.getCategory()).setSelected(true); getToggleForCategory(file.getCategory()).setSelected(true);
} }
public void activateTileViewer() { public void activateTileViewer() {
groupViewMode.set(GroupViewMode.TILE); groupViewMode.set(GroupViewMode.TILE);
tileToggle.setSelected(true); tileToggle.setSelected(true);
@ -322,11 +322,11 @@ public class GroupPane extends BorderPane {
slideShowPane = null; slideShowPane = null;
this.scrollToFileID(selectionModel.lastSelectedProperty().get()); this.scrollToFileID(selectionModel.lastSelectedProperty().get());
} }
public DrawableGroup getGroup() { public DrawableGroup getGroup() {
return grouping.get(); return grouping.get();
} }
private void selectAllFiles() { private void selectAllFiles() {
selectionModel.clearAndSelectAll(getGroup().getFileIDs()); selectionModel.clearAndSelectAll(getGroup().getFileIDs());
} }
@ -343,15 +343,15 @@ public class GroupPane extends BorderPane {
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()), : Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
getGroup().getHashSetHitsCount(), getGroup().getSize()); getGroup().getHashSetHitsCount(), getGroup().getSize());
} }
ContextMenu getContextMenu() { ContextMenu getContextMenu() {
return contextMenu; return contextMenu;
} }
ReadOnlyObjectProperty<DrawableGroup> grouping() { ReadOnlyObjectProperty<DrawableGroup> grouping() {
return grouping.getReadOnlyProperty(); return grouping.getReadOnlyProperty();
} }
private ToggleButton getToggleForCategory(DhsImageCategory category) { private ToggleButton getToggleForCategory(DhsImageCategory category) {
switch (category) { switch (category) {
case ZERO: case ZERO:
@ -396,7 +396,7 @@ public class GroupPane extends BorderPane {
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'.";
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'.";
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'.";
for (DhsImageCategory cat : DhsImageCategory.values()) { for (DhsImageCategory cat : DhsImageCategory.values()) {
ToggleButton toggleForCategory = getToggleForCategory(cat); ToggleButton toggleForCategory = getToggleForCategory(cat);
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2))); toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
@ -421,20 +421,16 @@ public class GroupPane extends BorderPane {
gridView.cellHeightProperty().bind(cellSize); gridView.cellHeightProperty().bind(cellSize);
gridView.cellWidthProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize);
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell()); gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
Platform.runLater(() -> { Platform.runLater(() -> {
try { TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS
TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); tagSelectedSplitMenu.setText(followUpSelectedACtion.getText());
tagSelectedSplitMenu.setText(followUpSelectedACtion.getText()); tagSelectedSplitMenu.setGraphic(followUpSelectedACtion.getGraphic());
tagSelectedSplitMenu.setGraphic(followUpSelectedACtion.getGraphic()); tagSelectedSplitMenu.setOnAction(followUpSelectedACtion);
tagSelectedSplitMenu.setOnAction(followUpSelectedACtion);
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.WARNING, "failed to load FollowUpTagName", tskCoreException); //NON-NLS
}
tagSelectedSplitMenu.showingProperty().addListener(showing -> { tagSelectedSplitMenu.showingProperty().addListener(showing -> {
if (tagSelectedSplitMenu.isShowing()) { if (tagSelectedSplitMenu.isShowing()) {
List<MenuItem> selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), List<MenuItem> selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
@ -442,9 +438,9 @@ public class GroupPane extends BorderPane {
tagSelectedSplitMenu.getItems().setAll(selTagMenues); tagSelectedSplitMenu.getItems().setAll(selTagMenues);
} }
}); });
}); });
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller); CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText()); catSelectedSplitMenu.setText(cat5SelectedAction.getText());
@ -456,12 +452,12 @@ public class GroupPane extends BorderPane {
catSelectedSplitMenu.getItems().setAll(categoryMenues); catSelectedSplitMenu.getItems().setAll(categoryMenues);
} }
}); });
slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().remove("radio-button");
slideShowToggle.getStyleClass().add("toggle-button"); slideShowToggle.getStyleClass().add("toggle-button");
tileToggle.getStyleClass().remove("radio-button"); tileToggle.getStyleClass().remove("radio-button");
tileToggle.getStyleClass().add("toggle-button"); tileToggle.getStyleClass().add("toggle-button");
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText()); bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText()); headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText()); catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
@ -481,12 +477,12 @@ public class GroupPane extends BorderPane {
//listen to toggles and update view state //listen to toggles and update view state
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get())); slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
tileToggle.setOnAction(onAction -> activateTileViewer()); tileToggle.setOnAction(onAction -> activateTileViewer());
controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState));
addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler);
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler()); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
ActionUtils.configureButton(undoAction, undoButton); ActionUtils.configureButton(undoAction, undoButton);
ActionUtils.configureButton(redoAction, redoButton); ActionUtils.configureButton(redoAction, redoButton);
ActionUtils.configureButton(forwardAction, forwardButton); ActionUtils.configureButton(forwardAction, forwardButton);
@ -502,7 +498,7 @@ public class GroupPane extends BorderPane {
nextButton.setEffect(null); nextButton.setEffect(null);
onAction.handle(actionEvent); onAction.handle(actionEvent);
}); });
nextGroupAction.disabledProperty().addListener((Observable observable) -> { nextGroupAction.disabledProperty().addListener((Observable observable) -> {
boolean newValue = nextGroupAction.isDisabled(); boolean newValue = nextGroupAction.isDisabled();
nextButton.setEffect(newValue ? null : DROP_SHADOW); nextButton.setEffect(newValue ? null : DROP_SHADOW);
@ -516,13 +512,13 @@ public class GroupPane extends BorderPane {
//listen to tile selection and make sure it is visible in scroll area //listen to tile selection and make sure it is visible in scroll area
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
&& slideShowPane != null) { && slideShowPane != null) {
slideShowPane.setFile(newFileId); slideShowPane.setFile(newFileId);
} else { } else {
scrollToFileID(newFileId); scrollToFileID(newFileId);
} }
}); });
setViewState(controller.viewState().get()); setViewState(controller.viewState().get());
} }
@ -532,16 +528,16 @@ public class GroupPane extends BorderPane {
if (newFileID == null) { if (newFileID == null) {
return; //scrolling to no file doesn't make sense, so abort. return; //scrolling to no file doesn't make sense, so abort.
} }
final ObservableList<Long> fileIds = gridView.getItems(); final ObservableList<Long> fileIds = gridView.getItems();
int selectedIndex = fileIds.indexOf(newFileID); int selectedIndex = fileIds.indexOf(newFileID);
if (selectedIndex == -1) { if (selectedIndex == -1) {
//somehow we got passed a file id that isn't in the curent group. //somehow we got passed a file id that isn't in the curent group.
//this should never happen, but if it does everything is going to fail, so abort. //this should never happen, but if it does everything is going to fail, so abort.
return; return;
} }
getScrollBar().ifPresent(scrollBar -> { getScrollBar().ifPresent(scrollBar -> {
DrawableCell cell = cellMap.get(newFileID); DrawableCell cell = cellMap.get(newFileID);
@ -568,14 +564,14 @@ public class GroupPane extends BorderPane {
} }
cell = cellMap.get(newFileID); cell = cellMap.get(newFileID);
} }
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal()); final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal()); Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates //while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
int i = 0; int i = 0;
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) { while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
if (tileBounds.getMinY() < gridViewBounds.getMinY()) { if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
scrollBar.decrement(); scrollBar.decrement();
} else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) { } else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) {
@ -593,13 +589,13 @@ public class GroupPane extends BorderPane {
* @param grouping the new grouping assigned to this group * @param grouping the new grouping assigned to this group
*/ */
void setViewState(GroupViewState viewState) { void setViewState(GroupViewState viewState) {
if (isNull(viewState) || isNull(viewState.getGroup())) { if (isNull(viewState) || isNull(viewState.getGroup())) {
if (nonNull(getGroup())) { if (nonNull(getGroup())) {
getGroup().getFileIDs().removeListener(filesSyncListener); getGroup().getFileIDs().removeListener(filesSyncListener);
} }
this.grouping.set(null); this.grouping.set(null);
Platform.runLater(() -> { Platform.runLater(() -> {
gridView.getItems().setAll(Collections.emptyList()); gridView.getItems().setAll(Collections.emptyList());
setCenter(null); setCenter(null);
@ -611,18 +607,18 @@ public class GroupPane extends BorderPane {
cellMap.clear(); cellMap.clear();
} }
}); });
} else { } else {
if (getGroup() != viewState.getGroup()) { if (getGroup() != viewState.getGroup()) {
if (nonNull(getGroup())) { if (nonNull(getGroup())) {
getGroup().getFileIDs().removeListener(filesSyncListener); getGroup().getFileIDs().removeListener(filesSyncListener);
} }
this.grouping.set(viewState.getGroup()); this.grouping.set(viewState.getGroup());
getGroup().getFileIDs().addListener(filesSyncListener); getGroup().getFileIDs().addListener(filesSyncListener);
final String header = getHeaderString(); final String header = getHeaderString();
Platform.runLater(() -> { Platform.runLater(() -> {
gridView.getItems().setAll(getGroup().getFileIDs()); gridView.getItems().setAll(getGroup().getFileIDs());
slideShowToggle.setDisable(gridView.getItems().isEmpty()); slideShowToggle.setDisable(gridView.getItems().isEmpty());
@ -637,14 +633,14 @@ public class GroupPane extends BorderPane {
} }
} }
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private void resetScrollBar() { private void resetScrollBar() {
getScrollBar().ifPresent((scrollBar) -> { getScrollBar().ifPresent((scrollBar) -> {
scrollBar.setValue(0); scrollBar.setValue(0);
}); });
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private Optional<ScrollBar> getScrollBar() { private Optional<ScrollBar> getScrollBar() {
if (gridView == null || gridView.getSkin() == null) { if (gridView == null || gridView.getSkin() == null) {
@ -652,16 +648,16 @@ public class GroupPane extends BorderPane {
} }
return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS
} }
void makeSelection(Boolean shiftDown, Long newFileID) { void makeSelection(Boolean shiftDown, Long newFileID) {
if (shiftDown) { if (shiftDown) {
//TODO: do more hear to implement slicker multiselect //TODO: do more hear to implement slicker multiselect
int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt(); int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt();
List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1); List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1);
selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()]));
selectionModel.select(newFileID); selectionModel.select(newFileID);
} else { } else {
@ -669,11 +665,11 @@ public class GroupPane extends BorderPane {
selectionModel.clearAndSelect(newFileID); selectionModel.clearAndSelect(newFileID);
} }
} }
private class DrawableCell extends GridCell<Long> { private class DrawableCell extends GridCell<Long> {
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller); private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
DrawableCell() { DrawableCell() {
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> { itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
if (oldValue != null) { if (oldValue != null) {
@ -689,19 +685,19 @@ public class GroupPane extends BorderPane {
} }
} }
cellMap.put(newValue, DrawableCell.this); cellMap.put(newValue, DrawableCell.this);
} }
}); });
setGraphic(tile); setGraphic(tile);
} }
@Override @Override
protected void updateItem(Long item, boolean empty) { protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
tile.setFile(item); tile.setFile(item);
} }
void resetItem() { void resetItem() {
tile.setFile(null); tile.setFile(null);
} }
@ -712,10 +708,10 @@ public class GroupPane extends BorderPane {
* arrows) * arrows)
*/ */
private class KeyboardHandler implements EventHandler<KeyEvent> { private class KeyboardHandler implements EventHandler<KeyEvent> {
@Override @Override
public void handle(KeyEvent t) { public void handle(KeyEvent t) {
if (t.getEventType() == KeyEvent.KEY_PRESSED) { if (t.getEventType() == KeyEvent.KEY_PRESSED) {
switch (t.getCode()) { switch (t.getCode()) {
case SHIFT: case SHIFT:
@ -758,7 +754,7 @@ public class GroupPane extends BorderPane {
t.consume(); t.consume();
break; break;
} }
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
selectAllFiles(); selectAllFiles();
t.consume(); t.consume();
@ -772,7 +768,7 @@ public class GroupPane extends BorderPane {
} }
} }
} }
private DhsImageCategory keyCodeToCat(KeyCode t) { private DhsImageCategory keyCodeToCat(KeyCode t) {
if (t != null) { if (t != null) {
switch (t) { switch (t) {
@ -798,16 +794,16 @@ public class GroupPane extends BorderPane {
} }
return null; return null;
} }
private void handleArrows(KeyEvent t) { private void handleArrows(KeyEvent t) {
Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
int lastSelectedIndex = lastSelectFileId != null int lastSelectedIndex = lastSelectFileId != null
? grouping.get().getFileIDs().indexOf(lastSelectFileId) ? grouping.get().getFileIDs().indexOf(lastSelectFileId)
: Optional.ofNullable(selectionAnchorIndex).orElse(0); : Optional.ofNullable(selectionAnchorIndex).orElse(0);
final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1); final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1);
final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1); final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1);
// implement proper keyboard based multiselect // implement proper keyboard based multiselect
@ -826,19 +822,17 @@ public class GroupPane extends BorderPane {
} }
} }
} }
private class MouseHandler implements EventHandler<MouseEvent> { private class MouseHandler implements EventHandler<MouseEvent> {
private ContextMenu buildContextMenu() { private ContextMenu buildContextMenu() {
ArrayList<MenuItem> menuItems = new ArrayList<>(); ArrayList<MenuItem> menuItems = new ArrayList<>();
menuItems.add(CategorizeAction.getCategoriesMenu(controller)); menuItems.add(CategorizeAction.getCategoriesMenu(controller));
menuItems.add(AddTagAction.getTagMenu(controller)); menuItems.add(AddTagAction.getTagMenu(controller));
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
for (ContextMenuActionsProvider provider : menuProviders) { for (ContextMenuActionsProvider provider : menuProviders) {
for (final Action act : provider.getActions()) { for (final Action act : provider.getActions()) {
if (act instanceof Presenter.Popup) { if (act instanceof Presenter.Popup) {
@ -855,12 +849,12 @@ public class GroupPane extends BorderPane {
}); });
}); });
menuItems.add(extractMenuItem); menuItems.add(extractMenuItem);
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{})); ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
contextMenu.setAutoHide(true); contextMenu.setAutoHide(true);
return contextMenu; return contextMenu;
} }
@Override @Override
public void handle(MouseEvent t) { public void handle(MouseEvent t) {
switch (t.getButton()) { switch (t.getButton()) {
@ -881,7 +875,7 @@ public class GroupPane extends BorderPane {
if (contextMenu == null) { if (contextMenu == null) {
contextMenu = buildContextMenu(); contextMenu = buildContextMenu();
} }
contextMenu.hide(); contextMenu.hide();
contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY()); contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY());
} }