diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java index 7742ec583c..b633c08ced 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java @@ -25,7 +25,6 @@ import javafx.event.ActionEvent; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javax.swing.SwingUtilities; - import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; @@ -33,7 +32,6 @@ import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog; import org.sleuthkit.autopsy.actions.GetTagNameDialog; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.datamodel.TagName; /** @@ -74,9 +72,9 @@ abstract class AddTagAction { // @@@ This user interface has some significant usability issues and needs // to be reworked. @NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag", - "AddTagAction.menuItem.noTags=No tags", - "AddTagAction.menuItem.newTag=New Tag...", - "AddTagAction.menuItem.tagAndComment=Tag and Comment..."}) + "AddTagAction.menuItem.noTags=No tags", + "AddTagAction.menuItem.newTag=New Tag...", + "AddTagAction.menuItem.tagAndComment=Tag and Comment..."}) protected class TagMenu extends Menu { TagMenu(ImageGalleryController controller) { @@ -133,11 +131,7 @@ abstract class AddTagAction { SwingUtilities.invokeLater(() -> { GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow()); if (null != tagNameAndComment) { - if (CategoryManager.isCategoryTagName(tagNameAndComment.getTagName())) { - new CategorizeAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); - } else { - new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); - } + new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); } }); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 8c819373c8..ab59df29e4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,26 +21,28 @@ package org.sleuthkit.autopsy.imagegallery.actions; import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.event.ActionEvent; +import javafx.collections.ObservableSet; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; +import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import javax.swing.JOptionPane; - +import org.controlsfx.control.action.Action; +import org.controlsfx.control.action.ActionUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; +import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.datamodel.ContentTag; @@ -49,48 +51,42 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * Adaptation of Tag Actions to enforce category-tag uniqueness * - * TODO: since we are not using actionsGlobalContext anymore and this has - * diverged from autopsy action, make this extend from controlsfx Action */ @NbBundle.Messages({"CategorizeAction.displayName=Categorize"}) -public class CategorizeAction extends AddTagAction { +public class CategorizeAction extends Action { private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName()); private final ImageGalleryController controller; private final UndoRedoManager undoManager; + private final Category cat; + private final Set selectedFileIDs; + private final Boolean createUndo; - public CategorizeAction(ImageGalleryController controller) { - super(); - this.controller = controller; - undoManager = controller.getUndoManager(); + public CategorizeAction(ImageGalleryController controller, Category cat, Set selectedFileIDs) { + this(controller, cat, selectedFileIDs, true); } - public Menu getPopupMenu() { + private CategorizeAction(ImageGalleryController controller, Category cat, Set selectedFileIDs, Boolean createUndo) { + super(cat.getDisplayName()); + this.controller = controller; + this.undoManager = controller.getUndoManager(); + this.cat = cat; + this.selectedFileIDs = selectedFileIDs; + this.createUndo = createUndo; + setGraphic(cat.getGraphic()); + setEventHandler(actionEvent -> addCatToFiles()); + setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber())))); + } + + static public Menu getCategoriesMenu(ImageGalleryController controller) { return new CategoryMenu(controller); } - @Override - protected String getActionDisplayName() { - return Bundle.CategorizeAction_displayName(); - } - - @Override - public void addTag(TagName tagName, String comment) { - Set selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected()); - addTagsToFiles(tagName, comment, selectedFiles); - } - - @Override - protected void addTagsToFiles(TagName tagName, String comment, Set selectedFiles) { - addTagsToFiles(tagName, comment, selectedFiles, true); - } - - public void addTagsToFiles(TagName tagName, String comment, Set selectedFiles, boolean createUndo) { - Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFiles.toString(), tagName.getDisplayName()}); //NON-NLS - controller.queueDBWorkerTask(new CategorizeTask(selectedFiles, tagName, comment, createUndo)); + private void addCatToFiles() { + Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFileIDs.toString(), cat.getDisplayName()}); //NON-NLS + controller.queueDBWorkerTask(new CategorizeTask(selectedFileIDs, cat, createUndo)); } /** @@ -101,49 +97,43 @@ public class CategorizeAction extends AddTagAction { CategoryMenu(ImageGalleryController controller) { super(Bundle.CategorizeAction_displayName()); + setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon())); + ObservableSet selected = controller.getSelectionModel().getSelected(); // Each category get an item in the sub-menu. Selecting one of these menu items adds // a tag with the associated category. for (final Category cat : Category.values()) { - - MenuItem categoryItem = new MenuItem(cat.getDisplayName()); - categoryItem.setOnAction((ActionEvent t) -> { - final CategorizeAction categorizeAction = new CategorizeAction(controller); - categorizeAction.addTag(controller.getCategoryManager().getTagName(cat), NO_COMMENT); - }); - categoryItem.setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber())))); + MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected)); getItems().add(categoryItem); } } } @NbBundle.Messages({"# {0} - fileID number", - "CategorizeTask.errorUnable.msg=Unable to categorize {0}.", - "CategorizeTask.errorUnable.title=Categorizing Error"}) + "CategorizeTask.errorUnable.msg=Unable to categorize {0}.", + "CategorizeTask.errorUnable.title=Categorizing Error"}) private class CategorizeTask extends ImageGalleryController.InnerTask { private final Set fileIDs; - @Nonnull - private final TagName tagName; - private final String comment; - private final boolean createUndo; - CategorizeTask(Set fileIDs, @Nonnull TagName tagName, String comment, boolean createUndo) { + private final boolean createUndo; + private final Category cat; + + CategorizeTask(Set fileIDs, @Nonnull Category cat, boolean createUndo) { super(); this.fileIDs = fileIDs; - java.util.Objects.requireNonNull(tagName); - this.tagName = tagName; - this.comment = comment; + java.util.Objects.requireNonNull(cat); + this.cat = cat; this.createUndo = createUndo; - } - @Override public void run() { final DrawableTagsManager tagsManager = controller.getTagsManager(); final CategoryManager categoryManager = controller.getCategoryManager(); - Map oldCats = new HashMap<>(); + Map oldCats = new HashMap<>(); + TagName tagName = categoryManager.getTagName(cat); + TagName catZeroTagName = categoryManager.getTagName(Category.ZERO); for (long fileID : fileIDs) { try { DrawableFile file = controller.getFileFromId(fileID); //drawable db access @@ -151,12 +141,12 @@ public class CategorizeAction extends AddTagAction { Category oldCat = file.getCategory(); //drawable db access TagName oldCatTagName = categoryManager.getTagName(oldCat); if (false == tagName.equals(oldCatTagName)) { - oldCats.put(fileID, oldCatTagName); + oldCats.put(fileID, oldCat); } } final List fileTags = tagsManager.getContentTagsByContent(file); - if (tagName == categoryManager.getTagName(Category.ZERO)) { + if (tagName.equals(catZeroTagName)) { // delete all cat tags for cat-0 fileTags.stream() .filter(tag -> CategoryManager.isCategoryTagName(tag.getName())) @@ -173,7 +163,7 @@ public class CategorizeAction extends AddTagAction { .map(Tag::getName) .filter(tagName::equals) .collect(Collectors.toList()).isEmpty()) { - tagsManager.addContentTag(file, tagName, comment); + tagsManager.addContentTag(file, tagName, ""); } } } catch (TskCoreException ex) { @@ -186,7 +176,7 @@ public class CategorizeAction extends AddTagAction { } if (createUndo && oldCats.isEmpty() == false) { - undoManager.addToUndo(new CategorizationChange(controller, tagName, oldCats)); + undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats)); } } } @@ -197,11 +187,11 @@ public class CategorizeAction extends AddTagAction { @Immutable private final class CategorizationChange implements UndoRedoManager.UndoableCommand { - private final TagName newCategory; - private final ImmutableMap oldCategories; + private final Category newCategory; + private final ImmutableMap oldCategories; private final ImageGalleryController controller; - CategorizationChange(ImageGalleryController controller, TagName newCategory, Map oldCategories) { + CategorizationChange(ImageGalleryController controller, Category newCategory, Map oldCategories) { this.controller = controller; this.newCategory = newCategory; this.oldCategories = ImmutableMap.copyOf(oldCategories); @@ -213,8 +203,8 @@ public class CategorizeAction extends AddTagAction { */ @Override public void run() { - CategorizeAction categorizeAction = new CategorizeAction(controller); - categorizeAction.addTagsToFiles(newCategory, "", this.oldCategories.keySet(), false); + CategorizeAction categorizeAction = new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false); + categorizeAction.addCatToFiles(); } /** @@ -223,9 +213,10 @@ public class CategorizeAction extends AddTagAction { */ @Override public void undo() { - CategorizeAction categorizeAction = new CategorizeAction(controller); - for (Map.Entry entry : oldCategories.entrySet()) { - categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false); + + for (Map.Entry entry : oldCategories.entrySet()) { + new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false) + .addCatToFiles(); } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index b060b645c9..00df7e1fc0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -18,8 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; -import com.google.common.collect.ImmutableSet; -import java.util.Set; +import java.util.HashSet; import org.controlsfx.control.action.Action; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; @@ -30,10 +29,9 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.Category; public class CategorizeGroupAction extends Action { public CategorizeGroupAction(Category cat, ImageGalleryController controller) { - super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> { - Set fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs()); - new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet); - }); - setGraphic(cat.getGraphic()); + super(cat.getDisplayName(), actionEvent -> + new CategorizeAction(controller, cat, new HashSet<>(controller.viewState().get().getGroup().getFileIDs())) + .handle(actionEvent) + ); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java index 04a2a93e9e..7290ffb6ca 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java @@ -18,17 +18,15 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; -import org.controlsfx.control.action.Action; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; /** * */ -public class CategorizeSelectedFilesAction extends Action { +public class CategorizeSelectedFilesAction extends CategorizeAction { public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) { - super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), "")); - setGraphic(cat.getGraphic()); + super(controller, cat, controller.getSelectionModel().getSelected()); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java index 36d84f90e7..1b2bd604b0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java @@ -25,6 +25,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javafx.geometry.Insets; import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.Border; @@ -94,6 +97,7 @@ public enum Category { private final String displayName; private final int id; + private Image snapshot; private Category(Color color, int id, String name) { this.color = color; @@ -118,11 +122,15 @@ public enum Category { return displayName; } - public Node getGraphic() { - Region region = new Region(); - region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY))); - region.setPrefSize(16, 16); - region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2))); - return region; + synchronized public Node getGraphic() { + if (snapshot == null) { + Region region = new Region(); + region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY))); + region.setPrefSize(16, 16); + region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2))); + Scene scene = new Scene(region, 16, 16, Color.TRANSPARENT); + snapshot = region.snapshot(null, null); + } + return new ImageView(snapshot); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index b25b76f7ff..a353d97cc0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -142,7 +142,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { * @param controller the value of controller */ @NbBundle.Messages({"DrawableTileBase.menuItem.extractFiles=Extract File(s)", - "DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"}) + "DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"}) protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) { super(controller); this.groupPane = groupPane; @@ -182,11 +182,10 @@ public abstract class DrawableTileBase extends DrawableUIBase { private ContextMenu buildContextMenu(DrawableFile file) { final ArrayList menuItems = new ArrayList<>(); - menuItems.add(new CategorizeAction(getController()).getPopupMenu()); + menuItems.add(CategorizeAction.getCategoriesMenu(getController())); menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu()); - final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles()); extractMenuItem.setOnAction(actionEvent -> { SwingUtilities.invokeLater(() -> { @@ -196,7 +195,6 @@ public abstract class DrawableTileBase extends DrawableUIBase { }); menuItems.add(extractMenuItem); - MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer()); contentViewer.setOnAction(actionEvent -> { SwingUtilities.invokeLater(() -> { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 160b6ccbcc..6cb63fb84c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Arrays; @@ -47,6 +48,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; @@ -143,39 +145,39 @@ import org.sleuthkit.datamodel.TskCoreException; * https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview */ public class GroupPane extends BorderPane { - + private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2); private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2); - + private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE); - + private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR)) ); - + private final FileIDSelectionModel selectionModel; private static final List categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); - + private final Back backAction; - + private final Forward forwardAction; - + @FXML private Button undoButton; @FXML private Button redoButton; - + @FXML private SplitMenuButton catSelectedSplitMenu; - + @FXML private SplitMenuButton tagSelectedSplitMenu; - + @FXML private ToolBar headerToolBar; - + @FXML private ToggleButton cat0Toggle; @FXML @@ -188,30 +190,30 @@ public class GroupPane extends BorderPane { private ToggleButton cat4Toggle; @FXML private ToggleButton cat5Toggle; - + @FXML private SegmentedButton segButton; - + private SlideShowView slideShowPane; - + @FXML private ToggleButton slideShowToggle; - + @FXML private GridView gridView; - + @FXML private ToggleButton tileToggle; - + @FXML private Button nextButton; - + @FXML private Button backButton; - + @FXML private Button forwardButton; - + @FXML private Label groupLabel; @FXML @@ -222,24 +224,24 @@ public class GroupPane extends BorderPane { private Label catContainerLabel; @FXML private Label catHeadingLabel; - + @FXML private HBox catSegmentedContainer; @FXML private HBox catSplitMenuContainer; - + private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); - + private final NextUnseenGroup nextGroupAction; - + private final ImageGalleryController controller; - + private ContextMenu contextMenu; - + private Integer selectionAnchorIndex; private final UndoAction undoAction; private final RedoAction redoAction; - + GroupViewMode getGroupViewMode() { return groupViewMode.get(); } @@ -262,7 +264,7 @@ public class GroupPane extends BorderPane { */ @ThreadConfined(type = ThreadType.JFX) private final Map cellMap = new HashMap<>(); - + private final InvalidationListener filesSyncListener = (observable) -> { final String header = getHeaderString(); final List fileIds = getGroup().getFileIDs(); @@ -272,7 +274,7 @@ public class GroupPane extends BorderPane { groupLabel.setText(header); }); }; - + public GroupPane(ImageGalleryController controller) { this.controller = controller; this.selectionModel = controller.getSelectionModel(); @@ -281,10 +283,10 @@ public class GroupPane extends BorderPane { forwardAction = new Forward(controller); undoAction = new UndoAction(controller); redoAction = new RedoAction(controller); - + FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS } - + @ThreadConfined(type = ThreadType.JFX) public void activateSlideShowViewer(Long slideShowFileID) { groupViewMode.set(GroupViewMode.SLIDE_SHOW); @@ -300,16 +302,16 @@ public class GroupPane extends BorderPane { } else { slideShowPane.setFile(slideShowFileID); } - + setCenter(slideShowPane); slideShowPane.requestFocus(); - + } - + void syncCatToggle(DrawableFile file) { getToggleForCategory(file.getCategory()).setSelected(true); } - + public void activateTileViewer() { groupViewMode.set(GroupViewMode.TILE); tileToggle.setSelected(true); @@ -321,11 +323,11 @@ public class GroupPane extends BorderPane { slideShowPane = null; this.scrollToFileID(selectionModel.lastSelectedProperty().get()); } - + public DrawableGroup getGroup() { return grouping.get(); } - + private void selectAllFiles() { selectionModel.clearAndSelectAll(getGroup().getFileIDs()); } @@ -342,15 +344,15 @@ public class GroupPane extends BorderPane { : Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()), getGroup().getHashSetHitsCount(), getGroup().getSize()); } - + ContextMenu getContextMenu() { return contextMenu; } - + ReadOnlyObjectProperty grouping() { return grouping.getReadOnlyProperty(); } - + private ToggleButton getToggleForCategory(Category category) { switch (category) { case ZERO: @@ -377,10 +379,10 @@ public class GroupPane extends BorderPane { */ @FXML @NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)", - "GroupPane.bottomLabel.displayText=Group Viewing History: ", - "GroupPane.hederLabel.displayText=Tag Selected Files:", - "GroupPane.catContainerLabel.displayText=Categorize Selected File:", - "GroupPane.catHeadingLabel.displayText=Category:"}) + "GroupPane.bottomLabel.displayText=Group Viewing History: ", + "GroupPane.hederLabel.displayText=Tag Selected Files:", + "GroupPane.catContainerLabel.displayText=Categorize Selected File:", + "GroupPane.catHeadingLabel.displayText=Category:"}) void initialize() { assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; @@ -395,7 +397,7 @@ public class GroupPane extends BorderPane { assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'."; - + for (Category cat : Category.values()) { ToggleButton toggleForCategory = getToggleForCategory(cat); toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2))); @@ -403,9 +405,9 @@ public class GroupPane extends BorderPane { toggleForCategory.getStyleClass().add("toggle-button"); toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> { if (toggleSelected && slideShowPane != null) { - slideShowPane.getFileID().ifPresent((fileID) -> { + slideShowPane.getFileID().ifPresent(fileID -> { selectionModel.clearAndSelect(fileID); - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), ""); + new CategorizeAction(controller, cat, ImmutableSet.of(fileID)).handle(null); }); } }); @@ -420,11 +422,11 @@ public class GroupPane extends BorderPane { gridView.cellHeightProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize); gridView.setCellFactory((GridView param) -> new DrawableCell()); - + BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); - + Platform.runLater(() -> { try { TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); @@ -441,9 +443,9 @@ public class GroupPane extends BorderPane { tagSelectedSplitMenu.getItems().setAll(selTagMenues); } }); - + }); - + CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller); catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setText(cat5SelectedAction.getText()); @@ -455,12 +457,12 @@ public class GroupPane extends BorderPane { catSelectedSplitMenu.getItems().setAll(categoryMenues); } }); - + slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().add("toggle-button"); tileToggle.getStyleClass().remove("radio-button"); tileToggle.getStyleClass().add("toggle-button"); - + bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText()); headerLabel.setText(Bundle.GroupPane_hederLabel_displayText()); catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText()); @@ -480,12 +482,12 @@ public class GroupPane extends BorderPane { //listen to toggles and update view state slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get())); tileToggle.setOnAction(onAction -> activateTileViewer()); - + controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); - + addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler()); - + ActionUtils.configureButton(undoAction, undoButton); ActionUtils.configureButton(redoAction, redoButton); ActionUtils.configureButton(forwardAction, forwardButton); @@ -501,7 +503,7 @@ public class GroupPane extends BorderPane { nextButton.setEffect(null); onAction.handle(actionEvent); }); - + nextGroupAction.disabledProperty().addListener((Observable observable) -> { boolean newValue = nextGroupAction.isDisabled(); nextButton.setEffect(newValue ? null : DROP_SHADOW); @@ -521,7 +523,7 @@ public class GroupPane extends BorderPane { scrollToFileID(newFileId); } }); - + setViewState(controller.viewState().get()); } @@ -531,16 +533,16 @@ public class GroupPane extends BorderPane { if (newFileID == null) { return; //scrolling to no file doesn't make sense, so abort. } - + final ObservableList fileIds = gridView.getItems(); - + int selectedIndex = fileIds.indexOf(newFileID); if (selectedIndex == -1) { //somehow we got passed a file id that isn't in the curent group. //this should never happen, but if it does everything is going to fail, so abort. return; } - + getScrollBar().ifPresent(scrollBar -> { DrawableCell cell = cellMap.get(newFileID); @@ -567,14 +569,14 @@ public class GroupPane extends BorderPane { } cell = cellMap.get(newFileID); } - + final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal()); Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal()); //while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates int i = 0; while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) { - + if (tileBounds.getMinY() < gridViewBounds.getMinY()) { scrollBar.decrement(); } else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) { @@ -592,13 +594,13 @@ public class GroupPane extends BorderPane { * @param grouping the new grouping assigned to this group */ void setViewState(GroupViewState viewState) { - + if (isNull(viewState) || isNull(viewState.getGroup())) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } this.grouping.set(null); - + Platform.runLater(() -> { gridView.getItems().setAll(Collections.emptyList()); setCenter(null); @@ -610,18 +612,18 @@ public class GroupPane extends BorderPane { cellMap.clear(); } }); - + } else { if (getGroup() != viewState.getGroup()) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } this.grouping.set(viewState.getGroup()); - + getGroup().getFileIDs().addListener(filesSyncListener); - + final String header = getHeaderString(); - + Platform.runLater(() -> { gridView.getItems().setAll(getGroup().getFileIDs()); slideShowToggle.setDisable(gridView.getItems().isEmpty()); @@ -636,14 +638,14 @@ public class GroupPane extends BorderPane { } } } - + @ThreadConfined(type = ThreadType.JFX) private void resetScrollBar() { getScrollBar().ifPresent((scrollBar) -> { scrollBar.setValue(0); }); } - + @ThreadConfined(type = ThreadType.JFX) private Optional getScrollBar() { if (gridView == null || gridView.getSkin() == null) { @@ -651,16 +653,16 @@ public class GroupPane extends BorderPane { } return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS } - + void makeSelection(Boolean shiftDown, Long newFileID) { - + if (shiftDown) { //TODO: do more hear to implement slicker multiselect int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); List subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1); - + selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); selectionModel.select(newFileID); } else { @@ -668,11 +670,11 @@ public class GroupPane extends BorderPane { selectionModel.clearAndSelect(newFileID); } } - + private class DrawableCell extends GridCell { - + private final DrawableTile tile = new DrawableTile(GroupPane.this, controller); - + DrawableCell() { itemProperty().addListener((ObservableValue observable, Long oldValue, Long newValue) -> { if (oldValue != null) { @@ -688,19 +690,19 @@ public class GroupPane extends BorderPane { } } cellMap.put(newValue, DrawableCell.this); - + } }); - + setGraphic(tile); } - + @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); tile.setFile(item); } - + void resetItem() { tile.setFile(null); } @@ -711,10 +713,10 @@ public class GroupPane extends BorderPane { * arrows) */ private class KeyboardHandler implements EventHandler { - + @Override public void handle(KeyEvent t) { - + if (t.getEventType() == KeyEvent.KEY_PRESSED) { switch (t.getCode()) { case SHIFT: @@ -757,51 +759,56 @@ public class GroupPane extends BorderPane { t.consume(); break; } - + if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { selectAllFiles(); t.consume(); } - if (selectionModel.getSelected().isEmpty() == false) { - switch (t.getCode()) { - case NUMPAD0: - case DIGIT0: - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ZERO), ""); - break; - case NUMPAD1: - case DIGIT1: - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ONE), ""); - break; - case NUMPAD2: - case DIGIT2: - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.TWO), ""); - break; - case NUMPAD3: - case DIGIT3: - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.THREE), ""); - break; - case NUMPAD4: - case DIGIT4: - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FOUR), ""); - break; - case NUMPAD5: - case DIGIT5: - new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FIVE), ""); - break; + ObservableSet selected = selectionModel.getSelected(); + if (selected.isEmpty() == false) { + Category cat = keyCodeToCat(t.getCode()); + if (cat != null) { + new CategorizeAction(controller, cat, selected).handle(null); } } } } - + + private Category keyCodeToCat(KeyCode t) { + if (t != null) { + switch (t) { + case NUMPAD0: + case DIGIT0: + return Category.ZERO; + case NUMPAD1: + case DIGIT1: + return Category.ONE; + case NUMPAD2: + case DIGIT2: + return Category.TWO; + case NUMPAD3: + case DIGIT3: + return Category.THREE; + case NUMPAD4: + case DIGIT4: + return Category.FOUR; + case NUMPAD5: + case DIGIT5: + return Category.FIVE; + } + } + return null; + } + private void handleArrows(KeyEvent t) { Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); - + int lastSelectedIndex = lastSelectFileId != null ? grouping.get().getFileIDs().indexOf(lastSelectFileId) : Optional.ofNullable(selectionAnchorIndex).orElse(0); - + final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1); - + final Map tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1); // implement proper keyboard based multiselect @@ -820,17 +827,17 @@ public class GroupPane extends BorderPane { } } } - + private class MouseHandler implements EventHandler { - + private ContextMenu buildContextMenu() { ArrayList menuItems = new ArrayList<>(); - - menuItems.add(new CategorizeAction(controller).getPopupMenu()); + + menuItems.add(CategorizeAction.getCategoriesMenu(controller)); menuItems.add(new AddDrawableTagAction(controller).getPopupMenu()); - + Collection menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); - + for (ContextMenuActionsProvider provider : menuProviders) { for (final Action act : provider.getActions()) { if (act instanceof Presenter.Popup) { @@ -847,12 +854,12 @@ public class GroupPane extends BorderPane { }); }); menuItems.add(extractMenuItem); - + ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{})); contextMenu.setAutoHide(true); return contextMenu; } - + @Override public void handle(MouseEvent t) { switch (t.getButton()) { @@ -873,7 +880,7 @@ public class GroupPane extends BorderPane { if (contextMenu == null) { contextMenu = buildContextMenu(); } - + contextMenu.hide(); contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY()); }