Merge pull request #1963 from millmanorama/IG-cat-colors-in-menus

add category color icons to the right click menus in IG
This commit is contained in:
Richard Cordovano 2016-02-12 16:19:57 -05:00
commit e90d429b84
7 changed files with 212 additions and 218 deletions

View File

@ -25,7 +25,6 @@ import javafx.event.ActionEvent;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.windows.TopComponent; import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager; import org.openide.windows.WindowManager;
@ -33,7 +32,6 @@ import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
import org.sleuthkit.autopsy.actions.GetTagNameDialog; import org.sleuthkit.autopsy.actions.GetTagNameDialog;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
/** /**
@ -74,9 +72,9 @@ abstract class AddTagAction {
// @@@ This user interface has some significant usability issues and needs // @@@ This user interface has some significant usability issues and needs
// to be reworked. // to be reworked.
@NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag", @NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag",
"AddTagAction.menuItem.noTags=No tags", "AddTagAction.menuItem.noTags=No tags",
"AddTagAction.menuItem.newTag=New Tag...", "AddTagAction.menuItem.newTag=New Tag...",
"AddTagAction.menuItem.tagAndComment=Tag and Comment..."}) "AddTagAction.menuItem.tagAndComment=Tag and Comment..."})
protected class TagMenu extends Menu { protected class TagMenu extends Menu {
TagMenu(ImageGalleryController controller) { TagMenu(ImageGalleryController controller) {
@ -133,11 +131,7 @@ abstract class AddTagAction {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow()); GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow());
if (null != tagNameAndComment) { if (null != tagNameAndComment) {
if (CategoryManager.isCategoryTagName(tagNameAndComment.getTagName())) { new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
new CategorizeAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
} else {
new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
}
} }
}); });
}); });

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,26 +21,28 @@ package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javafx.event.ActionEvent; import javafx.collections.ObservableSet;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCodeCombination;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
@ -49,48 +51,42 @@ import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Adaptation of Tag Actions to enforce category-tag uniqueness
* *
* TODO: since we are not using actionsGlobalContext anymore and this has
* diverged from autopsy action, make this extend from controlsfx Action
*/ */
@NbBundle.Messages({"CategorizeAction.displayName=Categorize"}) @NbBundle.Messages({"CategorizeAction.displayName=Categorize"})
public class CategorizeAction extends AddTagAction { public class CategorizeAction extends Action {
private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName());
private final ImageGalleryController controller; private final ImageGalleryController controller;
private final UndoRedoManager undoManager; private final UndoRedoManager undoManager;
private final Category cat;
private final Set<Long> selectedFileIDs;
private final Boolean createUndo;
public CategorizeAction(ImageGalleryController controller) { public CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs) {
super(); this(controller, cat, selectedFileIDs, true);
this.controller = controller;
undoManager = controller.getUndoManager();
} }
public Menu getPopupMenu() { private CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs, Boolean createUndo) {
super(cat.getDisplayName());
this.controller = controller;
this.undoManager = controller.getUndoManager();
this.cat = cat;
this.selectedFileIDs = selectedFileIDs;
this.createUndo = createUndo;
setGraphic(cat.getGraphic());
setEventHandler(actionEvent -> addCatToFiles());
setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
}
static public Menu getCategoriesMenu(ImageGalleryController controller) {
return new CategoryMenu(controller); return new CategoryMenu(controller);
} }
@Override private void addCatToFiles() {
protected String getActionDisplayName() { Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFileIDs.toString(), cat.getDisplayName()}); //NON-NLS
return Bundle.CategorizeAction_displayName(); controller.queueDBWorkerTask(new CategorizeTask(selectedFileIDs, cat, createUndo));
}
@Override
public void addTag(TagName tagName, String comment) {
Set<Long> selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected());
addTagsToFiles(tagName, comment, selectedFiles);
}
@Override
protected void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
addTagsToFiles(tagName, comment, selectedFiles, true);
}
public void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles, boolean createUndo) {
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFiles.toString(), tagName.getDisplayName()}); //NON-NLS
controller.queueDBWorkerTask(new CategorizeTask(selectedFiles, tagName, comment, createUndo));
} }
/** /**
@ -101,49 +97,43 @@ public class CategorizeAction extends AddTagAction {
CategoryMenu(ImageGalleryController controller) { CategoryMenu(ImageGalleryController controller) {
super(Bundle.CategorizeAction_displayName()); super(Bundle.CategorizeAction_displayName());
setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon()));
ObservableSet<Long> selected = controller.getSelectionModel().getSelected();
// Each category get an item in the sub-menu. Selecting one of these menu items adds // Each category get an item in the sub-menu. Selecting one of these menu items adds
// a tag with the associated category. // a tag with the associated category.
for (final Category cat : Category.values()) { for (final Category cat : Category.values()) {
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected));
MenuItem categoryItem = new MenuItem(cat.getDisplayName());
categoryItem.setOnAction((ActionEvent t) -> {
final CategorizeAction categorizeAction = new CategorizeAction(controller);
categorizeAction.addTag(controller.getCategoryManager().getTagName(cat), NO_COMMENT);
});
categoryItem.setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
getItems().add(categoryItem); getItems().add(categoryItem);
} }
} }
} }
@NbBundle.Messages({"# {0} - fileID number", @NbBundle.Messages({"# {0} - fileID number",
"CategorizeTask.errorUnable.msg=Unable to categorize {0}.", "CategorizeTask.errorUnable.msg=Unable to categorize {0}.",
"CategorizeTask.errorUnable.title=Categorizing Error"}) "CategorizeTask.errorUnable.title=Categorizing Error"})
private class CategorizeTask extends ImageGalleryController.InnerTask { private class CategorizeTask extends ImageGalleryController.InnerTask {
private final Set<Long> fileIDs; private final Set<Long> fileIDs;
@Nonnull
private final TagName tagName;
private final String comment;
private final boolean createUndo;
CategorizeTask(Set<Long> fileIDs, @Nonnull TagName tagName, String comment, boolean createUndo) { private final boolean createUndo;
private final Category cat;
CategorizeTask(Set<Long> fileIDs, @Nonnull Category cat, boolean createUndo) {
super(); super();
this.fileIDs = fileIDs; this.fileIDs = fileIDs;
java.util.Objects.requireNonNull(tagName); java.util.Objects.requireNonNull(cat);
this.tagName = tagName; this.cat = cat;
this.comment = comment;
this.createUndo = createUndo; this.createUndo = createUndo;
} }
@Override @Override
public void run() { public void run() {
final DrawableTagsManager tagsManager = controller.getTagsManager(); final DrawableTagsManager tagsManager = controller.getTagsManager();
final CategoryManager categoryManager = controller.getCategoryManager(); final CategoryManager categoryManager = controller.getCategoryManager();
Map<Long, TagName> oldCats = new HashMap<>(); Map<Long, Category> oldCats = new HashMap<>();
TagName tagName = categoryManager.getTagName(cat);
TagName catZeroTagName = categoryManager.getTagName(Category.ZERO);
for (long fileID : fileIDs) { for (long fileID : fileIDs) {
try { try {
DrawableFile<?> file = controller.getFileFromId(fileID); //drawable db access DrawableFile<?> file = controller.getFileFromId(fileID); //drawable db access
@ -151,12 +141,12 @@ public class CategorizeAction extends AddTagAction {
Category oldCat = file.getCategory(); //drawable db access Category oldCat = file.getCategory(); //drawable db access
TagName oldCatTagName = categoryManager.getTagName(oldCat); TagName oldCatTagName = categoryManager.getTagName(oldCat);
if (false == tagName.equals(oldCatTagName)) { if (false == tagName.equals(oldCatTagName)) {
oldCats.put(fileID, oldCatTagName); oldCats.put(fileID, oldCat);
} }
} }
final List<ContentTag> fileTags = tagsManager.getContentTagsByContent(file); final List<ContentTag> fileTags = tagsManager.getContentTagsByContent(file);
if (tagName == categoryManager.getTagName(Category.ZERO)) { if (tagName.equals(catZeroTagName)) {
// delete all cat tags for cat-0 // delete all cat tags for cat-0
fileTags.stream() fileTags.stream()
.filter(tag -> CategoryManager.isCategoryTagName(tag.getName())) .filter(tag -> CategoryManager.isCategoryTagName(tag.getName()))
@ -173,7 +163,7 @@ public class CategorizeAction extends AddTagAction {
.map(Tag::getName) .map(Tag::getName)
.filter(tagName::equals) .filter(tagName::equals)
.collect(Collectors.toList()).isEmpty()) { .collect(Collectors.toList()).isEmpty()) {
tagsManager.addContentTag(file, tagName, comment); tagsManager.addContentTag(file, tagName, "");
} }
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
@ -186,7 +176,7 @@ public class CategorizeAction extends AddTagAction {
} }
if (createUndo && oldCats.isEmpty() == false) { if (createUndo && oldCats.isEmpty() == false) {
undoManager.addToUndo(new CategorizationChange(controller, tagName, oldCats)); undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats));
} }
} }
} }
@ -197,11 +187,11 @@ public class CategorizeAction extends AddTagAction {
@Immutable @Immutable
private final class CategorizationChange implements UndoRedoManager.UndoableCommand { private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
private final TagName newCategory; private final Category newCategory;
private final ImmutableMap<Long, TagName> oldCategories; private final ImmutableMap<Long, Category> oldCategories;
private final ImageGalleryController controller; private final ImageGalleryController controller;
CategorizationChange(ImageGalleryController controller, TagName newCategory, Map<Long, TagName> oldCategories) { CategorizationChange(ImageGalleryController controller, Category newCategory, Map<Long, Category> oldCategories) {
this.controller = controller; this.controller = controller;
this.newCategory = newCategory; this.newCategory = newCategory;
this.oldCategories = ImmutableMap.copyOf(oldCategories); this.oldCategories = ImmutableMap.copyOf(oldCategories);
@ -213,8 +203,8 @@ public class CategorizeAction extends AddTagAction {
*/ */
@Override @Override
public void run() { public void run() {
CategorizeAction categorizeAction = new CategorizeAction(controller); CategorizeAction categorizeAction = new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false);
categorizeAction.addTagsToFiles(newCategory, "", this.oldCategories.keySet(), false); categorizeAction.addCatToFiles();
} }
/** /**
@ -223,9 +213,10 @@ public class CategorizeAction extends AddTagAction {
*/ */
@Override @Override
public void undo() { public void undo() {
CategorizeAction categorizeAction = new CategorizeAction(controller);
for (Map.Entry<Long, TagName> entry : oldCategories.entrySet()) { for (Map.Entry<Long, Category> entry : oldCategories.entrySet()) {
categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false); new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false)
.addCatToFiles();
} }
} }
} }

View File

@ -18,8 +18,7 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.collect.ImmutableSet; import java.util.HashSet;
import java.util.Set;
import org.controlsfx.control.action.Action; import org.controlsfx.control.action.Action;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
@ -30,10 +29,9 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
public class CategorizeGroupAction extends Action { public class CategorizeGroupAction extends Action {
public CategorizeGroupAction(Category cat, ImageGalleryController controller) { public CategorizeGroupAction(Category cat, ImageGalleryController controller) {
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> { super(cat.getDisplayName(), actionEvent ->
Set<Long> fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs()); new CategorizeAction(controller, cat, new HashSet<>(controller.viewState().get().getGroup().getFileIDs()))
new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet); .handle(actionEvent)
}); );
setGraphic(cat.getGraphic());
} }
} }

View File

@ -18,17 +18,15 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import org.controlsfx.control.action.Action;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
/** /**
* *
*/ */
public class CategorizeSelectedFilesAction extends Action { public class CategorizeSelectedFilesAction extends CategorizeAction {
public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) { public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) {
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), "")); super(controller, cat, controller.getSelectionModel().getSelected());
setGraphic(cat.getGraphic());
} }
} }

View File

@ -25,6 +25,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background; import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border; import javafx.scene.layout.Border;
@ -94,6 +97,7 @@ public enum Category {
private final String displayName; private final String displayName;
private final int id; private final int id;
private Image snapshot;
private Category(Color color, int id, String name) { private Category(Color color, int id, String name) {
this.color = color; this.color = color;
@ -118,11 +122,15 @@ public enum Category {
return displayName; return displayName;
} }
public Node getGraphic() { synchronized public Node getGraphic() {
Region region = new Region(); if (snapshot == null) {
region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY))); Region region = new Region();
region.setPrefSize(16, 16); region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY)));
region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2))); region.setPrefSize(16, 16);
return region; region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2)));
Scene scene = new Scene(region, 16, 16, Color.TRANSPARENT);
snapshot = region.snapshot(null, null);
}
return new ImageView(snapshot);
} }
} }

View File

@ -142,7 +142,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
* @param controller the value of controller * @param controller the value of controller
*/ */
@NbBundle.Messages({"DrawableTileBase.menuItem.extractFiles=Extract File(s)", @NbBundle.Messages({"DrawableTileBase.menuItem.extractFiles=Extract File(s)",
"DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"}) "DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"})
protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) { protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) {
super(controller); super(controller);
this.groupPane = groupPane; this.groupPane = groupPane;
@ -182,11 +182,10 @@ public abstract class DrawableTileBase extends DrawableUIBase {
private ContextMenu buildContextMenu(DrawableFile<?> file) { private ContextMenu buildContextMenu(DrawableFile<?> file) {
final ArrayList<MenuItem> menuItems = new ArrayList<>(); final ArrayList<MenuItem> menuItems = new ArrayList<>();
menuItems.add(new CategorizeAction(getController()).getPopupMenu()); menuItems.add(CategorizeAction.getCategoriesMenu(getController()));
menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu()); menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu());
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles()); final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
extractMenuItem.setOnAction(actionEvent -> { extractMenuItem.setOnAction(actionEvent -> {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
@ -196,7 +195,6 @@ public abstract class DrawableTileBase extends DrawableUIBase {
}); });
menuItems.add(extractMenuItem); menuItems.add(extractMenuItem);
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer()); MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
contentViewer.setOnAction(actionEvent -> { contentViewer.setOnAction(actionEvent -> {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -47,6 +48,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -143,39 +145,39 @@ import org.sleuthkit.datamodel.TskCoreException;
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview * https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
*/ */
public class GroupPane extends BorderPane { public class GroupPane extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName());
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2); private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2); private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE); private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR)) new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
); );
private final FileIDSelectionModel selectionModel; private final FileIDSelectionModel selectionModel;
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5,
KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5);
private final Back backAction; private final Back backAction;
private final Forward forwardAction; private final Forward forwardAction;
@FXML @FXML
private Button undoButton; private Button undoButton;
@FXML @FXML
private Button redoButton; private Button redoButton;
@FXML @FXML
private SplitMenuButton catSelectedSplitMenu; private SplitMenuButton catSelectedSplitMenu;
@FXML @FXML
private SplitMenuButton tagSelectedSplitMenu; private SplitMenuButton tagSelectedSplitMenu;
@FXML @FXML
private ToolBar headerToolBar; private ToolBar headerToolBar;
@FXML @FXML
private ToggleButton cat0Toggle; private ToggleButton cat0Toggle;
@FXML @FXML
@ -188,30 +190,30 @@ public class GroupPane extends BorderPane {
private ToggleButton cat4Toggle; private ToggleButton cat4Toggle;
@FXML @FXML
private ToggleButton cat5Toggle; private ToggleButton cat5Toggle;
@FXML @FXML
private SegmentedButton segButton; private SegmentedButton segButton;
private SlideShowView slideShowPane; private SlideShowView slideShowPane;
@FXML @FXML
private ToggleButton slideShowToggle; private ToggleButton slideShowToggle;
@FXML @FXML
private GridView<Long> gridView; private GridView<Long> gridView;
@FXML @FXML
private ToggleButton tileToggle; private ToggleButton tileToggle;
@FXML @FXML
private Button nextButton; private Button nextButton;
@FXML @FXML
private Button backButton; private Button backButton;
@FXML @FXML
private Button forwardButton; private Button forwardButton;
@FXML @FXML
private Label groupLabel; private Label groupLabel;
@FXML @FXML
@ -222,24 +224,24 @@ public class GroupPane extends BorderPane {
private Label catContainerLabel; private Label catContainerLabel;
@FXML @FXML
private Label catHeadingLabel; private Label catHeadingLabel;
@FXML @FXML
private HBox catSegmentedContainer; private HBox catSegmentedContainer;
@FXML @FXML
private HBox catSplitMenuContainer; private HBox catSplitMenuContainer;
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
private final NextUnseenGroup nextGroupAction; private final NextUnseenGroup nextGroupAction;
private final ImageGalleryController controller; private final ImageGalleryController controller;
private ContextMenu contextMenu; private ContextMenu contextMenu;
private Integer selectionAnchorIndex; private Integer selectionAnchorIndex;
private final UndoAction undoAction; private final UndoAction undoAction;
private final RedoAction redoAction; private final RedoAction redoAction;
GroupViewMode getGroupViewMode() { GroupViewMode getGroupViewMode() {
return groupViewMode.get(); return groupViewMode.get();
} }
@ -262,7 +264,7 @@ public class GroupPane extends BorderPane {
*/ */
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private final Map<Long, DrawableCell> cellMap = new HashMap<>(); private final Map<Long, DrawableCell> cellMap = new HashMap<>();
private final InvalidationListener filesSyncListener = (observable) -> { private final InvalidationListener filesSyncListener = (observable) -> {
final String header = getHeaderString(); final String header = getHeaderString();
final List<Long> fileIds = getGroup().getFileIDs(); final List<Long> fileIds = getGroup().getFileIDs();
@ -272,7 +274,7 @@ public class GroupPane extends BorderPane {
groupLabel.setText(header); groupLabel.setText(header);
}); });
}; };
public GroupPane(ImageGalleryController controller) { public GroupPane(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
this.selectionModel = controller.getSelectionModel(); this.selectionModel = controller.getSelectionModel();
@ -281,10 +283,10 @@ public class GroupPane extends BorderPane {
forwardAction = new Forward(controller); forwardAction = new Forward(controller);
undoAction = new UndoAction(controller); undoAction = new UndoAction(controller);
redoAction = new RedoAction(controller); redoAction = new RedoAction(controller);
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
public void activateSlideShowViewer(Long slideShowFileID) { public void activateSlideShowViewer(Long slideShowFileID) {
groupViewMode.set(GroupViewMode.SLIDE_SHOW); groupViewMode.set(GroupViewMode.SLIDE_SHOW);
@ -300,16 +302,16 @@ public class GroupPane extends BorderPane {
} else { } else {
slideShowPane.setFile(slideShowFileID); slideShowPane.setFile(slideShowFileID);
} }
setCenter(slideShowPane); setCenter(slideShowPane);
slideShowPane.requestFocus(); slideShowPane.requestFocus();
} }
void syncCatToggle(DrawableFile<?> file) { void syncCatToggle(DrawableFile<?> file) {
getToggleForCategory(file.getCategory()).setSelected(true); getToggleForCategory(file.getCategory()).setSelected(true);
} }
public void activateTileViewer() { public void activateTileViewer() {
groupViewMode.set(GroupViewMode.TILE); groupViewMode.set(GroupViewMode.TILE);
tileToggle.setSelected(true); tileToggle.setSelected(true);
@ -321,11 +323,11 @@ public class GroupPane extends BorderPane {
slideShowPane = null; slideShowPane = null;
this.scrollToFileID(selectionModel.lastSelectedProperty().get()); this.scrollToFileID(selectionModel.lastSelectedProperty().get());
} }
public DrawableGroup getGroup() { public DrawableGroup getGroup() {
return grouping.get(); return grouping.get();
} }
private void selectAllFiles() { private void selectAllFiles() {
selectionModel.clearAndSelectAll(getGroup().getFileIDs()); selectionModel.clearAndSelectAll(getGroup().getFileIDs());
} }
@ -342,15 +344,15 @@ public class GroupPane extends BorderPane {
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()), : Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
getGroup().getHashSetHitsCount(), getGroup().getSize()); getGroup().getHashSetHitsCount(), getGroup().getSize());
} }
ContextMenu getContextMenu() { ContextMenu getContextMenu() {
return contextMenu; return contextMenu;
} }
ReadOnlyObjectProperty<DrawableGroup> grouping() { ReadOnlyObjectProperty<DrawableGroup> grouping() {
return grouping.getReadOnlyProperty(); return grouping.getReadOnlyProperty();
} }
private ToggleButton getToggleForCategory(Category category) { private ToggleButton getToggleForCategory(Category category) {
switch (category) { switch (category) {
case ZERO: case ZERO:
@ -377,10 +379,10 @@ public class GroupPane extends BorderPane {
*/ */
@FXML @FXML
@NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)", @NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)",
"GroupPane.bottomLabel.displayText=Group Viewing History: ", "GroupPane.bottomLabel.displayText=Group Viewing History: ",
"GroupPane.hederLabel.displayText=Tag Selected Files:", "GroupPane.hederLabel.displayText=Tag Selected Files:",
"GroupPane.catContainerLabel.displayText=Categorize Selected File:", "GroupPane.catContainerLabel.displayText=Categorize Selected File:",
"GroupPane.catHeadingLabel.displayText=Category:"}) "GroupPane.catHeadingLabel.displayText=Category:"})
void initialize() { void initialize() {
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
@ -395,7 +397,7 @@ public class GroupPane extends BorderPane {
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'.";
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'.";
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'.";
for (Category cat : Category.values()) { for (Category cat : Category.values()) {
ToggleButton toggleForCategory = getToggleForCategory(cat); ToggleButton toggleForCategory = getToggleForCategory(cat);
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2))); toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
@ -403,9 +405,9 @@ public class GroupPane extends BorderPane {
toggleForCategory.getStyleClass().add("toggle-button"); toggleForCategory.getStyleClass().add("toggle-button");
toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> { toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> {
if (toggleSelected && slideShowPane != null) { if (toggleSelected && slideShowPane != null) {
slideShowPane.getFileID().ifPresent((fileID) -> { slideShowPane.getFileID().ifPresent(fileID -> {
selectionModel.clearAndSelect(fileID); selectionModel.clearAndSelect(fileID);
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), ""); new CategorizeAction(controller, cat, ImmutableSet.of(fileID)).handle(null);
}); });
} }
}); });
@ -420,11 +422,11 @@ public class GroupPane extends BorderPane {
gridView.cellHeightProperty().bind(cellSize); gridView.cellHeightProperty().bind(cellSize);
gridView.cellWidthProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize);
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell()); gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
Platform.runLater(() -> { Platform.runLater(() -> {
try { try {
TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller);
@ -441,9 +443,9 @@ public class GroupPane extends BorderPane {
tagSelectedSplitMenu.getItems().setAll(selTagMenues); tagSelectedSplitMenu.getItems().setAll(selTagMenues);
} }
}); });
}); });
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller); CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText()); catSelectedSplitMenu.setText(cat5SelectedAction.getText());
@ -455,12 +457,12 @@ public class GroupPane extends BorderPane {
catSelectedSplitMenu.getItems().setAll(categoryMenues); catSelectedSplitMenu.getItems().setAll(categoryMenues);
} }
}); });
slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().remove("radio-button");
slideShowToggle.getStyleClass().add("toggle-button"); slideShowToggle.getStyleClass().add("toggle-button");
tileToggle.getStyleClass().remove("radio-button"); tileToggle.getStyleClass().remove("radio-button");
tileToggle.getStyleClass().add("toggle-button"); tileToggle.getStyleClass().add("toggle-button");
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText()); bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText()); headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText()); catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
@ -480,12 +482,12 @@ public class GroupPane extends BorderPane {
//listen to toggles and update view state //listen to toggles and update view state
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get())); slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
tileToggle.setOnAction(onAction -> activateTileViewer()); tileToggle.setOnAction(onAction -> activateTileViewer());
controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState));
addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler);
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler()); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
ActionUtils.configureButton(undoAction, undoButton); ActionUtils.configureButton(undoAction, undoButton);
ActionUtils.configureButton(redoAction, redoButton); ActionUtils.configureButton(redoAction, redoButton);
ActionUtils.configureButton(forwardAction, forwardButton); ActionUtils.configureButton(forwardAction, forwardButton);
@ -501,7 +503,7 @@ public class GroupPane extends BorderPane {
nextButton.setEffect(null); nextButton.setEffect(null);
onAction.handle(actionEvent); onAction.handle(actionEvent);
}); });
nextGroupAction.disabledProperty().addListener((Observable observable) -> { nextGroupAction.disabledProperty().addListener((Observable observable) -> {
boolean newValue = nextGroupAction.isDisabled(); boolean newValue = nextGroupAction.isDisabled();
nextButton.setEffect(newValue ? null : DROP_SHADOW); nextButton.setEffect(newValue ? null : DROP_SHADOW);
@ -521,7 +523,7 @@ public class GroupPane extends BorderPane {
scrollToFileID(newFileId); scrollToFileID(newFileId);
} }
}); });
setViewState(controller.viewState().get()); setViewState(controller.viewState().get());
} }
@ -531,16 +533,16 @@ public class GroupPane extends BorderPane {
if (newFileID == null) { if (newFileID == null) {
return; //scrolling to no file doesn't make sense, so abort. return; //scrolling to no file doesn't make sense, so abort.
} }
final ObservableList<Long> fileIds = gridView.getItems(); final ObservableList<Long> fileIds = gridView.getItems();
int selectedIndex = fileIds.indexOf(newFileID); int selectedIndex = fileIds.indexOf(newFileID);
if (selectedIndex == -1) { if (selectedIndex == -1) {
//somehow we got passed a file id that isn't in the curent group. //somehow we got passed a file id that isn't in the curent group.
//this should never happen, but if it does everything is going to fail, so abort. //this should never happen, but if it does everything is going to fail, so abort.
return; return;
} }
getScrollBar().ifPresent(scrollBar -> { getScrollBar().ifPresent(scrollBar -> {
DrawableCell cell = cellMap.get(newFileID); DrawableCell cell = cellMap.get(newFileID);
@ -567,14 +569,14 @@ public class GroupPane extends BorderPane {
} }
cell = cellMap.get(newFileID); cell = cellMap.get(newFileID);
} }
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal()); final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal()); Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates //while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
int i = 0; int i = 0;
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) { while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
if (tileBounds.getMinY() < gridViewBounds.getMinY()) { if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
scrollBar.decrement(); scrollBar.decrement();
} else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) { } else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) {
@ -592,13 +594,13 @@ public class GroupPane extends BorderPane {
* @param grouping the new grouping assigned to this group * @param grouping the new grouping assigned to this group
*/ */
void setViewState(GroupViewState viewState) { void setViewState(GroupViewState viewState) {
if (isNull(viewState) || isNull(viewState.getGroup())) { if (isNull(viewState) || isNull(viewState.getGroup())) {
if (nonNull(getGroup())) { if (nonNull(getGroup())) {
getGroup().getFileIDs().removeListener(filesSyncListener); getGroup().getFileIDs().removeListener(filesSyncListener);
} }
this.grouping.set(null); this.grouping.set(null);
Platform.runLater(() -> { Platform.runLater(() -> {
gridView.getItems().setAll(Collections.emptyList()); gridView.getItems().setAll(Collections.emptyList());
setCenter(null); setCenter(null);
@ -610,18 +612,18 @@ public class GroupPane extends BorderPane {
cellMap.clear(); cellMap.clear();
} }
}); });
} else { } else {
if (getGroup() != viewState.getGroup()) { if (getGroup() != viewState.getGroup()) {
if (nonNull(getGroup())) { if (nonNull(getGroup())) {
getGroup().getFileIDs().removeListener(filesSyncListener); getGroup().getFileIDs().removeListener(filesSyncListener);
} }
this.grouping.set(viewState.getGroup()); this.grouping.set(viewState.getGroup());
getGroup().getFileIDs().addListener(filesSyncListener); getGroup().getFileIDs().addListener(filesSyncListener);
final String header = getHeaderString(); final String header = getHeaderString();
Platform.runLater(() -> { Platform.runLater(() -> {
gridView.getItems().setAll(getGroup().getFileIDs()); gridView.getItems().setAll(getGroup().getFileIDs());
slideShowToggle.setDisable(gridView.getItems().isEmpty()); slideShowToggle.setDisable(gridView.getItems().isEmpty());
@ -636,14 +638,14 @@ public class GroupPane extends BorderPane {
} }
} }
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private void resetScrollBar() { private void resetScrollBar() {
getScrollBar().ifPresent((scrollBar) -> { getScrollBar().ifPresent((scrollBar) -> {
scrollBar.setValue(0); scrollBar.setValue(0);
}); });
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private Optional<ScrollBar> getScrollBar() { private Optional<ScrollBar> getScrollBar() {
if (gridView == null || gridView.getSkin() == null) { if (gridView == null || gridView.getSkin() == null) {
@ -651,16 +653,16 @@ public class GroupPane extends BorderPane {
} }
return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS
} }
void makeSelection(Boolean shiftDown, Long newFileID) { void makeSelection(Boolean shiftDown, Long newFileID) {
if (shiftDown) { if (shiftDown) {
//TODO: do more hear to implement slicker multiselect //TODO: do more hear to implement slicker multiselect
int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt(); int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt();
List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1); List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1);
selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()]));
selectionModel.select(newFileID); selectionModel.select(newFileID);
} else { } else {
@ -668,11 +670,11 @@ public class GroupPane extends BorderPane {
selectionModel.clearAndSelect(newFileID); selectionModel.clearAndSelect(newFileID);
} }
} }
private class DrawableCell extends GridCell<Long> { private class DrawableCell extends GridCell<Long> {
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller); private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
DrawableCell() { DrawableCell() {
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> { itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
if (oldValue != null) { if (oldValue != null) {
@ -688,19 +690,19 @@ public class GroupPane extends BorderPane {
} }
} }
cellMap.put(newValue, DrawableCell.this); cellMap.put(newValue, DrawableCell.this);
} }
}); });
setGraphic(tile); setGraphic(tile);
} }
@Override @Override
protected void updateItem(Long item, boolean empty) { protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
tile.setFile(item); tile.setFile(item);
} }
void resetItem() { void resetItem() {
tile.setFile(null); tile.setFile(null);
} }
@ -711,10 +713,10 @@ public class GroupPane extends BorderPane {
* arrows) * arrows)
*/ */
private class KeyboardHandler implements EventHandler<KeyEvent> { private class KeyboardHandler implements EventHandler<KeyEvent> {
@Override @Override
public void handle(KeyEvent t) { public void handle(KeyEvent t) {
if (t.getEventType() == KeyEvent.KEY_PRESSED) { if (t.getEventType() == KeyEvent.KEY_PRESSED) {
switch (t.getCode()) { switch (t.getCode()) {
case SHIFT: case SHIFT:
@ -757,51 +759,56 @@ public class GroupPane extends BorderPane {
t.consume(); t.consume();
break; break;
} }
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
selectAllFiles(); selectAllFiles();
t.consume(); t.consume();
} }
if (selectionModel.getSelected().isEmpty() == false) { ObservableSet<Long> selected = selectionModel.getSelected();
switch (t.getCode()) { if (selected.isEmpty() == false) {
case NUMPAD0: Category cat = keyCodeToCat(t.getCode());
case DIGIT0: if (cat != null) {
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ZERO), ""); new CategorizeAction(controller, cat, selected).handle(null);
break;
case NUMPAD1:
case DIGIT1:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ONE), "");
break;
case NUMPAD2:
case DIGIT2:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.TWO), "");
break;
case NUMPAD3:
case DIGIT3:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.THREE), "");
break;
case NUMPAD4:
case DIGIT4:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FOUR), "");
break;
case NUMPAD5:
case DIGIT5:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FIVE), "");
break;
} }
} }
} }
} }
private Category keyCodeToCat(KeyCode t) {
if (t != null) {
switch (t) {
case NUMPAD0:
case DIGIT0:
return Category.ZERO;
case NUMPAD1:
case DIGIT1:
return Category.ONE;
case NUMPAD2:
case DIGIT2:
return Category.TWO;
case NUMPAD3:
case DIGIT3:
return Category.THREE;
case NUMPAD4:
case DIGIT4:
return Category.FOUR;
case NUMPAD5:
case DIGIT5:
return Category.FIVE;
}
}
return null;
}
private void handleArrows(KeyEvent t) { private void handleArrows(KeyEvent t) {
Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
int lastSelectedIndex = lastSelectFileId != null int lastSelectedIndex = lastSelectFileId != null
? grouping.get().getFileIDs().indexOf(lastSelectFileId) ? grouping.get().getFileIDs().indexOf(lastSelectFileId)
: Optional.ofNullable(selectionAnchorIndex).orElse(0); : Optional.ofNullable(selectionAnchorIndex).orElse(0);
final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1); final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1);
final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1); final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1);
// implement proper keyboard based multiselect // implement proper keyboard based multiselect
@ -820,17 +827,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(new CategorizeAction(controller).getPopupMenu()); menuItems.add(CategorizeAction.getCategoriesMenu(controller));
menuItems.add(new AddDrawableTagAction(controller).getPopupMenu()); menuItems.add(new AddDrawableTagAction(controller).getPopupMenu());
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
for (ContextMenuActionsProvider provider : menuProviders) { for (ContextMenuActionsProvider provider : menuProviders) {
for (final Action act : provider.getActions()) { for (final Action act : provider.getActions()) {
if (act instanceof Presenter.Popup) { if (act instanceof Presenter.Popup) {
@ -847,12 +854,12 @@ public class GroupPane extends BorderPane {
}); });
}); });
menuItems.add(extractMenuItem); menuItems.add(extractMenuItem);
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{})); ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
contextMenu.setAutoHide(true); contextMenu.setAutoHide(true);
return contextMenu; return contextMenu;
} }
@Override @Override
public void handle(MouseEvent t) { public void handle(MouseEvent t) {
switch (t.getButton()) { switch (t.getButton()) {
@ -873,7 +880,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());
} }