From a5a69b803604a5c7959daddb00f40b6b2297b70c Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 16 Jul 2013 18:07:37 -0400 Subject: [PATCH] First draft of implementation of multi-select tagging and export --- .../corecomponents/DataResultViewerTable.java | 2 +- .../datamodel/AbstractAbstractFileNode.java | 26 ++++- .../datamodel/AbstractContentChildren.java | 1 - .../autopsy/datamodel/DirectoryNode.java | 9 +- .../sleuthkit/autopsy/datamodel/FileNode.java | 14 ++- .../autopsy/datamodel/LayoutFileNode.java | 10 +- .../autopsy/datamodel/LocalFileNode.java | 12 +-- .../org/sleuthkit/autopsy/datamodel/Tags.java | 33 ------ .../datamodel/VirtualDirectoryNode.java | 11 +- .../directorytree/DataResultFilterNode.java | 45 ++++---- .../DirectoryTreeFilterNode.java | 4 +- .../ExplorerNodeActionVisitor.java | 25 +++-- .../autopsy/directorytree/ExtractAction.java | 96 ++++++----------- .../directorytree/TagAbstractFileAction.java | 51 +++++++++ .../autopsy/directorytree/TagAction.java | 102 ++++-------------- .../directorytree/TagAndCommentDialog.java | 75 +++++++------ .../TagBlackboardArtifactAction.java | 51 +++++++++ .../autopsy/directorytree/TagMenu.java | 73 ++++++------- 18 files changed, 318 insertions(+), 322 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java create mode 100755 Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 9ab0d40dea..6f5368578b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -62,7 +62,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ov.setAllowedDropActions(DnDConstants.ACTION_NONE); // only allow one item to be selected at a time - ov.getOutline().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + ov.getOutline().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // don't show the root node ov.getOutline().setRootVisible(false); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index adb6cb6249..51b2781c60 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.datamodel; import java.util.Map; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -33,6 +35,14 @@ public abstract class AbstractAbstractFileNode extends A private static Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName()); + /** + * These Actions are class instances to support multi-selection of nodes corresponding to AbstractFiles. + * They must be a class instances because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick + * up an Action if every selected node returns a reference to it from Node.getActions(boolean). + */ + private static TagAbstractFileAction tagAction = new TagAbstractFileAction(); + private static ExtractAction extractAction = new ExtractAction(); + /** * @param type of the AbstractFile data to encapsulate * @param abstractFile file to encapsulate @@ -153,8 +163,7 @@ public abstract class AbstractAbstractFileNode extends A } } } - - + /** * Fill map with AbstractFile properties * @@ -191,8 +200,15 @@ public abstract class AbstractAbstractFileNode extends A map.put(AbstractFilePropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash()); } - - static String getContentDisplayName(AbstractFile file) { + protected static TagAbstractFileAction getTagAbstractFileActionInstance() { + return tagAction; + } + + protected static ExtractAction getExtractActionInstance() { + return extractAction; + } + + protected static String getContentDisplayName(AbstractFile file) { String name = file.getName(); if (name.equals("..")) { name = DirectoryNode.DOTDOTDIR; @@ -201,4 +217,4 @@ public abstract class AbstractAbstractFileNode extends A } return name; } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 76ad367ace..3e10b86c93 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datamodel; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children.Keys; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsRootNode; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index a364071ac7..827552cfa2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -23,7 +23,6 @@ import java.util.List; import javax.swing.Action; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Directory; @@ -34,7 +33,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; * are more directories. */ public class DirectoryNode extends AbstractFsContentNode { - + public static final String DOTDOTDIR = "[parent folder]"; public static final String DOTDIR = "[current folder]"; @@ -67,16 +66,16 @@ public class DirectoryNode extends AbstractFsContentNode { */ @Override public Action[] getActions(boolean popup) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); if (!getDirectoryBrowseMode()) { actions.add(new ViewContextAction("View File in Directory", this)); actions.add(null); // creates a menu separator } actions.add(new NewWindowViewAction("View in New Window", this)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", this)); + actions.add(getExtractActionInstance()); actions.add(null); // creates a menu separator - actions.add(new TagAction(this)); + actions.add(getTagAbstractFileActionInstance()); return actions.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 6c3f739624..efbaf4e517 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -21,14 +21,13 @@ package org.sleuthkit.autopsy.datamodel; import java.util.ArrayList; import java.util.List; import javax.swing.Action; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.FsContent; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; @@ -37,7 +36,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; * files children. */ public class FileNode extends AbstractFsContentNode { - + /** * @param file underlying Content */ @@ -74,7 +73,7 @@ public class FileNode extends AbstractFsContentNode { */ @Override public Action[] getActions(boolean popup) { - List actionsList = new ArrayList(); + List actionsList = new ArrayList<>(); if (!this.getDirectoryBrowseMode()) { actionsList.add(new ViewContextAction("View File in Directory", this)); actionsList.add(null); // creates a menu separator @@ -82,10 +81,10 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new ExtractAction("Extract File", this)); + actionsList.add(getExtractActionInstance()); actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); - actionsList.add(null); // creates a menu separator - actionsList.add(new TagAction(this)); + actionsList.add(null); // creates a menu separator + actionsList.add(getTagAbstractFileActionInstance()); return actionsList.toArray(new Action[0]); } @@ -166,7 +165,6 @@ public class FileNode extends AbstractFsContentNode { } // Else return the default return "org/sleuthkit/autopsy/images/file-icon.png"; - } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 36f2fc8c02..899c8b045b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -24,11 +24,11 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; -import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getTagAbstractFileActionInstance; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.TskData; @@ -104,14 +104,12 @@ public class LayoutFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList(); - actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new ExtractAction("Extract File", content)); + actionsList.add(getExtractActionInstance()); actionsList.add(null); // creates a menu separator - actionsList.add(new TagAction(content)); - + actionsList.add(getTagAbstractFileActionInstance()); return actionsList.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index f2dc17d9a0..60d535e4ce 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -25,14 +25,14 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getTagAbstractFileActionInstance; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode.TYPE; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.LocalFile; /** * A Node for a LocalFile or DerivedFile content object. @@ -86,16 +86,14 @@ public class LocalFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { - List actionsList = new ArrayList(); - + List actionsList = new ArrayList<>(); actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new ExtractAction("Extract", content)); //might not need this actions - already local file + actionsList.add(getExtractActionInstance()); actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new TagAction(content)); - + actionsList.add(getTagAbstractFileActionInstance()); return actionsList.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 319d36e737..064f953660 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -22,7 +22,6 @@ import java.awt.event.ActionEvent; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; @@ -656,36 +655,4 @@ public class Tags implements AutopsyVisitableItem { return tagNames; } - - public interface Taggable { - void createTag(String name, String comment); - } - - public static class TaggableFile implements Taggable { - - private AbstractFile file; - - public TaggableFile(AbstractFile file) { - this.file = file; - } - - @Override - public void createTag(String name, String comment) { - Tags.createTag(file, name, comment); - } - } - - public static class TaggableBlackboardArtifact implements Taggable { - - private BlackboardArtifact bba; - - public TaggableBlackboardArtifact(BlackboardArtifact bba) { - this.bba = bba; - } - - @Override - public void createTag(String name, String comment) { - Tags.createTag(bba, name, comment); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index 59e523ff81..ac612e225f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -25,9 +25,9 @@ import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; import org.sleuthkit.autopsy.coreutils.Logger; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.TskData; @@ -76,16 +76,15 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode actions = new ArrayList(); - + List actions = new ArrayList<>(); actions.add(new NewWindowViewAction("View in New Window", this)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", this)); + actions.add(getExtractActionInstance()); actions.add(null); // creates a menu separator - actions.add(new TagAction(this)); + actions.add(getTagAbstractFileActionInstance()); return actions.toArray(new Action[0]); } - + @Override protected Sheet createSheet() { Sheet s = super.createSheet(); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index a439a066bf..6ea9191bf5 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -81,6 +81,15 @@ import org.sleuthkit.datamodel.VirtualDirectory; */ public class DataResultFilterNode extends FilterNode { + /** + * These are class instances to support multi-selection of nodes corresponding to AbstractFiles and BlackboardArtifacts. + * They are required because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every selected + * node returns a reference to it from Node.getActions(boolean). + */ + private final static Action extractAction = new ExtractAction(); + private final static Action fileTagAction = new TagAbstractFileAction(); + private final static Action resultTagAction = new TagBlackboardArtifactAction(); + private ExplorerManager sourceEm; private final DisplayableItemNodeVisitor> getActionsDIV; private final DisplayableItemNodeVisitor getPreferredActionsDIV; @@ -105,7 +114,7 @@ public class DataResultFilterNode extends FilterNode { @Override public Action[] getActions(boolean popup) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); final DisplayableItemNode originalNode = (DisplayableItemNode) this.getOriginal(); actions.addAll(originalNode.accept(getActionsDIV)); @@ -167,7 +176,7 @@ public class DataResultFilterNode extends FilterNode { //TODO all actions need to be consolidated in single place! //they should be set in individual Node subclass and using a utility to get Actions per Content sub-type - List actions = new ArrayList(); + List actions = new ArrayList<>(); //merge predefined specific node actions if bban subclasses have their own for (Action a : ban.getActions(true)) { @@ -197,15 +206,15 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", fn)); actions.add(new ExternalViewerAction("Open in External Viewer", fn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract File", new FileNode(f))); + actions.add(extractAction); actions.add(new HashSearchAction("Search for files with the same MD5 hash", fn)); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(f)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } if ((d = ban.getLookup().lookup(Directory.class)) != null) { @@ -214,14 +223,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", dn)); actions.add(new ExternalViewerAction("Open in External Viewer", dn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", dn)); + actions.add(extractAction); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(d)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } if ((vd = ban.getLookup().lookup(VirtualDirectory.class)) != null) { @@ -230,14 +239,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", dn)); actions.add(new ExternalViewerAction("Open in External Viewer", dn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", dn)); + actions.add(extractAction); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(d)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } else if ((lf = ban.getLookup().lookup(LayoutFile.class)) != null) { LayoutFileNode lfn = new LayoutFileNode(lf); @@ -245,14 +254,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", lfn)); actions.add(new ExternalViewerAction("Open in External Viewer", lfn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract File", lfn)); + actions.add(extractAction); //add tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(lf)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } else if ((locF = ban.getLookup().lookup(LocalFile.class)) != null || (locF = ban.getLookup().lookup(DerivedFile.class)) != null) { @@ -261,14 +270,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", locfn)); actions.add(new ExternalViewerAction("Open in External Viewer", locfn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract File", locfn)); + actions.add(extractAction); //add tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(lf)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } @@ -278,7 +287,7 @@ public class DataResultFilterNode extends FilterNode { @Override protected List defaultVisit(DisplayableItemNode ditem) { //preserve the default node's actions - List actions = new ArrayList(); + List actions = new ArrayList<>(); for (Action action : ditem.getActions(true)) { actions.add(action); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java index 018679ae2c..034e392e80 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java @@ -47,6 +47,7 @@ import org.sleuthkit.datamodel.TskCoreException; class DirectoryTreeFilterNode extends FilterNode { private static final Action collapseAll = new CollapseAction("Collapse All"); + private static final Action extractAction = new ExtractAction(); private static final Logger logger = Logger.getLogger(DirectoryTreeFilterNode.class.getName()); /** @@ -99,8 +100,7 @@ class DirectoryTreeFilterNode extends FilterNode { //extract dir action Directory dir = this.getLookup().lookup(Directory.class); if (dir != null) { - actions.add(new ExtractAction("Extract Directory", - getOriginal())); + actions.add(extractAction); } // file search action diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index 28479e83c3..96af70d439 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -47,6 +47,13 @@ import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.Volume; public class ExplorerNodeActionVisitor extends ContentVisitor.Default> { + /** + * These are class instances to support multi-selection of nodes corresponding to AbstractFiles and BlackboardArtifacts. + * They are required because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every selected + * node returns a reference to it from Node.getActions(boolean). + */ + private static Action extractAction = new ExtractAction(); + private static Action tagAction = new TagAbstractFileAction(); private static ExplorerNodeActionVisitor instance = new ExplorerNodeActionVisitor(); @@ -101,39 +108,39 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Directory d) { List actions = new ArrayList(); - actions.add(new TagAction(d)); + actions.add(tagAction); return actions; } @Override public List visit(final VirtualDirectory d) { List actions = new ArrayList(); - actions.add(new TagAction(d)); - actions.add(new ExtractAction("Extract Directory", d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } @Override public List visit(final DerivedFile d) { List actions = new ArrayList(); - actions.add(new ExtractAction("Extract File", d)); - actions.add(new TagAction(d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } @Override public List visit(final LocalFile d) { List actions = new ArrayList(); - actions.add(new ExtractAction("Extract File", d)); - actions.add(new TagAction(d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } @Override public List visit(final org.sleuthkit.datamodel.File d) { List actions = new ArrayList(); - actions.add(new ExtractAction("Extract File", d)); - actions.add(new TagAction(d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java index 6b183644f1..630b5d1a02 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java @@ -32,11 +32,15 @@ import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.nodes.Node; import org.openide.util.Cancellable; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; +import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; @@ -47,66 +51,12 @@ import org.sleuthkit.datamodel.Directory; */ public final class ExtractAction extends AbstractAction { - private static final InitializeContentVisitor initializeCV = new InitializeContentVisitor(); - private AbstractFile content; private Logger logger = Logger.getLogger(ExtractAction.class.getName()); - public ExtractAction(String title, Node contentNode) { - super(title); - Content tempContent = contentNode.getLookup().lookup(Content.class); - - this.content = tempContent.accept(initializeCV); - this.setEnabled(content != null); + public ExtractAction() { + super("Export"); } - public ExtractAction(String title, Content content) { - super(title); - - this.content = content.accept(initializeCV); - this.setEnabled(this.content != null); - } - - /** - * Returns the FsContent if it is supported, otherwise null - */ - private static class InitializeContentVisitor extends ContentVisitor.Default { - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.File f) { - return f; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LayoutFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.DerivedFile df) { - return df; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LocalFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.VirtualDirectory vd) { - return vd; - } - - @Override - public AbstractFile visit(Directory dir) { - return ContentUtils.isDotDirectory(dir) ? null : dir; - } - - @Override - protected AbstractFile defaultVisit(Content cntnt) { - return null; - } - } - /** * Asks user to choose destination, then extracts content/directory to * destination (recursing on directories) @@ -114,10 +64,35 @@ public final class ExtractAction extends AbstractAction { */ @Override public void actionPerformed(ActionEvent e) { + DataResultViewerTable resultViewer = (DataResultViewerTable)Lookup.getDefault().lookup(DataResultViewer.class); + if (null == resultViewer) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Could not get DataResultViewerTable from Lookup"); + return; + } + + Node[] selectedNodes = resultViewer.getExplorerManager().getSelectedNodes(); + if (selectedNodes.length <= 0) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Tried to perform tagging of Nodes with no Nodes selected"); + return; + } + + for (Node node : selectedNodes) { + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (null != file) { + extractFile(e, file); + } + else { + // RJCTODO +// Logger.getLogger(org.sleuthkit.autopsy.directorytree.TagAbstractFileAction.TagAbstractFileMenu.class.getName()).log(Level.SEVERE, "Node not associated with an AbstractFile object"); + } + } + } + + private void extractFile(ActionEvent e, AbstractFile file) { // Get content and check that it's okay to overwrite existing content JFileChooser fc = new JFileChooser(); fc.setCurrentDirectory(new File(Case.getCurrentCase().getCaseDirectory())); - fc.setSelectedFile(new File(this.content.getName())); + fc.setSelectedFile(new File(file.getName())); int returnValue = fc.showSaveDialog((Component) e.getSource()); if (returnValue == JFileChooser.APPROVE_OPTION) { @@ -144,12 +119,12 @@ public final class ExtractAction extends AbstractAction { try { ExtractFileThread extract = new ExtractFileThread(); - extract.init(this.content, e, destination); + extract.init(file, e, destination); extract.execute(); } catch (Exception ex) { logger.log(Level.WARNING, "Unable to start background thread.", ex); } - } + } } private class ExtractFileThread extends SwingWorker { @@ -230,6 +205,5 @@ public final class ExtractAction extends AbstractAction { } } } - } - + } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java new file mode 100755 index 0000000000..6ba7a518ed --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.directorytree; + +import java.util.logging.Level; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.Tags; +import org.sleuthkit.datamodel.AbstractFile; + +public class TagAbstractFileAction extends TagAction { + @Override + protected TagMenu getTagMenu(Node[] selectedNodes) { + return new TagAbstractFileMenu(selectedNodes); + } + + private static class TagAbstractFileMenu extends TagMenu { + public TagAbstractFileMenu(Node[] nodes) { + super((nodes.length > 1 ? "Tag Files" : "Tag File"), nodes); + } + + @Override + protected void tagNodes(String tagName, String comment) { + for (Node node : getNodes()) { + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (null != file) { + Tags.createTag(file, tagName, comment); + } + else { + Logger.getLogger(org.sleuthkit.autopsy.directorytree.TagAbstractFileAction.TagAbstractFileMenu.class.getName()).log(Level.SEVERE, "Node not associated with an AbstractFile object"); + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java index 35c755c0e2..436e3e4541 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java @@ -21,102 +21,38 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.event.ActionEvent; import java.util.logging.Level; import javax.swing.AbstractAction; -import javax.swing.JMenu; import javax.swing.JMenuItem; import org.openide.nodes.Node; import org.openide.util.actions.Presenter; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentVisitor; -import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; /** - * Action on a file or artifact that adds a tag and - * reloads the directory tree. Supports tagging of AbstractFiles and - * BlackboardArtifacts. - * - * TODO add use enters description and hierarchy (TSK_TAG_NAME with slashes) + * Action on a file or artifact that adds a tag and reloads the directory tree. + * Supports tagging of AbstractFiles and BlackboardArtifacts. */ -public class TagAction extends AbstractAction implements Presenter.Popup { - - private static final Logger logger = Logger.getLogger(TagAction.class.getName()); - private JMenu tagMenu; - private final InitializeBookmarkFileV initializer = new InitializeBookmarkFileV(); - - public TagAction(Node contentNode) { - AbstractFile file = contentNode.getLookup().lookup(AbstractFile.class); - if (file != null) { - tagMenu = new TagMenu(file); - return; - } - - BlackboardArtifact bba = contentNode.getLookup().lookup(BlackboardArtifact.class); - if (bba != null) { - tagMenu = new TagMenu(bba); - return; - } - - logger.log(Level.SEVERE, "Tried to create a " + TagAction.class.getName() - + " using a Node whose lookup did not contain an AbstractFile or a BlackboardArtifact."); - } - - public TagAction(AbstractFile file) { - tagMenu = new TagMenu(file); - } - - public TagAction(BlackboardArtifact bba) { - tagMenu = new TagMenu(bba); - } - +public abstract class TagAction extends AbstractAction implements Presenter.Popup { @Override public JMenuItem getPopupPresenter() { - return tagMenu; - } - - /** - * Returns the FsContent if it is supported, otherwise null - */ - private static class InitializeBookmarkFileV extends ContentVisitor.Default { - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.File f) { - return f; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LayoutFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.DerivedFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LocalFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.VirtualDirectory ld) { - return ld; - } - - @Override - public AbstractFile visit(Directory dir) { - return ContentUtils.isDotDirectory(dir) ? null : dir; - } - - @Override - protected AbstractFile defaultVisit(Content cntnt) { + DataResultViewerTable resultViewer = (DataResultViewerTable)Lookup.getDefault().lookup(DataResultViewer.class); + if (null == resultViewer) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Could not get DataResultViewerTable from Lookup"); return null; } + + Node[] selectedNodes = resultViewer.getExplorerManager().getSelectedNodes(); + if (selectedNodes.length <= 0) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Tried to perform tagging of Nodes with no Nodes selected"); + return null; + } + + return getTagMenu(selectedNodes); } + protected abstract TagMenu getTagMenu(Node[] selectedNodes); + @Override public void actionPerformed(ActionEvent e) { // Do nothing - this action should never be performed diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java index a36a78809b..6a66e55689 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java @@ -30,28 +30,50 @@ import javax.swing.JFrame; import javax.swing.KeyStroke; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Tags.Taggable; -import org.sleuthkit.datamodel.BlackboardArtifact; /** * Tag dialog for tagging files and results. User enters an optional comment. */ public class TagAndCommentDialog extends JDialog { - private static final String TAG_ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; - private static final String BOOKMARK_ICON_PATH = "org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"; - private static final String NO_TAG_MESSAGE = "No Tags"; - - private Taggable taggable; + private static final String NO_TAG_MESSAGE = "No Tags"; + private String tagName = ""; + private String comment = ""; + public static class CommentedTag { + private String name; + private String comment; + + CommentedTag(String name, String comment) { + this.name = name; + this.comment = comment; + } + + public String getName() { + return name; + } + + public String getComment() { + return comment; + } + } + + public static CommentedTag doDialog() { + TagAndCommentDialog dialog = new TagAndCommentDialog(); + if (!dialog.tagName.isEmpty()) { + return new CommentedTag(dialog.tagName, dialog.comment); + } + else { + return null; + } + } + /** * Creates new form TagDialog */ - public TagAndCommentDialog(Taggable taggable) { + private TagAndCommentDialog() { super((JFrame)WindowManager.getDefault().getMainWindow(), "Tag and Comment", true); - this.taggable = taggable; - initComponents(); // Close the dialog when Esc is pressed @@ -60,8 +82,8 @@ public class TagAndCommentDialog extends JDialog { inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); ActionMap actionMap = getRootPane().getActionMap(); actionMap.put(cancelName, new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { - //doClose(RET_CANCEL); dispose(); } }); @@ -81,15 +103,10 @@ public class TagAndCommentDialog extends JDialog { //center it this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); - - customizeComponent(); setVisible(true); // blocks } - - private void customizeComponent() { - } - + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -195,22 +212,12 @@ public class TagAndCommentDialog extends JDialog { }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - //doClose(RET_OK); - - // get the selected tag and comment - String selectedTag = (String)tagCombo.getSelectedItem(); - String comment = commentText.getText(); - - // create the tag - taggable.createTag(selectedTag, comment); - - refreshDirectoryTree(); - + tagName = (String)tagCombo.getSelectedItem(); + comment = commentText.getText(); dispose(); }//GEN-LAST:event_okButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed - //doClose(RET_CANCEL); dispose(); }//GEN-LAST:event_cancelButtonActionPerformed @@ -218,14 +225,12 @@ public class TagAndCommentDialog extends JDialog { * Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog - //doClose(RET_CANCEL); dispose(); }//GEN-LAST:event_closeDialog private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed String newTagName = CreateTagDialog.getNewTagNameDialog(null); if (newTagName != null) { - //tagsModel.addElement(newTagName); tagCombo.addItem(newTagName); tagCombo.setSelectedItem(newTagName); } @@ -240,12 +245,4 @@ public class TagAndCommentDialog extends JDialog { private javax.swing.JComboBox tagCombo; private javax.swing.JLabel tagLabel; // End of variables declaration//GEN-END:variables - //private int returnStatus = RET_CANCEL; - - private void refreshDirectoryTree() { - //TODO instead should send event to node children, which will call its refresh() / refreshKeys() - DirectoryTreeTopComponent viewer = DirectoryTreeTopComponent.findInstance(); - viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE); - viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT); - } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java new file mode 100755 index 0000000000..b8f7fefb96 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.directorytree; + +import java.util.logging.Level; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.Tags; +import org.sleuthkit.datamodel.BlackboardArtifact; + +public class TagBlackboardArtifactAction extends TagAction { + @Override + protected TagMenu getTagMenu(Node[] selectedNodes) { + return new TagBlackboardArtifactMenu(selectedNodes); + } + + private static class TagBlackboardArtifactMenu extends TagMenu { + public TagBlackboardArtifactMenu(Node[] nodes) { + super((nodes.length > 1 ? "Tag Results" : "Tag Result"), nodes); + } + + @Override + protected void tagNodes(String tagName, String comment) { + for (Node node : getNodes()) { + BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); + if (null != artifact) { + Tags.createTag(artifact, tagName, comment); + } + else { + Logger.getLogger(org.sleuthkit.autopsy.directorytree.TagBlackboardArtifactAction.TagBlackboardArtifactMenu.class.getName()).log(Level.SEVERE, "Node not associated with a BlackboardArtifact object"); + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java index af432e199a..9d6709f39b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java @@ -23,39 +23,26 @@ import java.awt.event.ActionListener; import java.util.List; import javax.swing.JMenu; import javax.swing.JMenuItem; +import org.openide.nodes.Node; import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Tags.Taggable; -import org.sleuthkit.autopsy.datamodel.Tags.TaggableBlackboardArtifact; -import org.sleuthkit.autopsy.datamodel.Tags.TaggableFile; -import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; /** * The menu that results when one right-clicks on a file or artifact. */ -public class TagMenu extends JMenu { +public abstract class TagMenu extends JMenu { - private Taggable tagCreator; - - public TagMenu(AbstractFile file) { - super("Tag File"); - tagCreator = new TaggableFile(file); - init(); - } - - public TagMenu(BlackboardArtifact bba) { - super("Tag Result"); - tagCreator = new TaggableBlackboardArtifact(bba); - init(); - } + private Node[] nodes; - private void init() { - - // create the 'Quick Tag' menu and add it to the 'Tag File' menu + public TagMenu(String menuItemText, Node[] selectedNodes) { + super(menuItemText); + this.nodes = selectedNodes; + + // Create the 'Quick Tag' sub-menu and add it to the tag menu. JMenu quickTagMenu = new JMenu("Quick Tag"); - add(quickTagMenu); - - // create the 'Quick Tag' sub-menu items and add them to the 'Quick Tag' menu + add(quickTagMenu); + + // Get the existing tag names. List tagNames = Tags.getTagNames(); if (tagNames.isEmpty()) { JMenuItem empty = new JMenuItem("No tags"); @@ -63,46 +50,56 @@ public class TagMenu extends JMenu { quickTagMenu.add(empty); } + // Add a menu item for each existing tag name to the 'Quick Tag' menu. for (final String tagName : tagNames) { - JMenuItem tagItem = new JMenuItem(tagName); - tagItem.addActionListener(new ActionListener() { + JMenuItem tagNameItem = new JMenuItem(tagName); + tagNameItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - tagCreator.createTag(tagName, ""); + tagNodes(tagName, ""); refreshDirectoryTree(); } }); - quickTagMenu.add(tagItem); + quickTagMenu.add(tagNameItem); } quickTagMenu.addSeparator(); - // create the 'New Tag' menu item + // Create the 'New Tag' menu item and add it to the 'Quick Tag' menu. JMenuItem newTagMenuItem = new JMenuItem("New Tag"); newTagMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - String newTagName = CreateTagDialog.getNewTagNameDialog(null); - if (newTagName != null) { - tagCreator.createTag(newTagName, ""); + String tagName = CreateTagDialog.getNewTagNameDialog(null); + if (tagName != null) { + tagNodes(tagName, ""); refreshDirectoryTree(); } } }); - - // add the 'New Tag' menu item to the 'Quick Tag' menu quickTagMenu.add(newTagMenuItem); - JMenuItem newTagItem = new JMenuItem("Tag and Comment"); - newTagItem.addActionListener(new ActionListener() { + // Create the 'Tag and Comment' menu item and add it to the tag menu. + JMenuItem tagAndCommentItem = new JMenuItem("Tag and Comment"); + tagAndCommentItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - new TagAndCommentDialog(tagCreator); + TagAndCommentDialog.CommentedTag commentedTag = TagAndCommentDialog.doDialog(); + if (null != commentedTag) { + tagNodes(commentedTag.getName(), commentedTag.getComment()); + refreshDirectoryTree(); + } } }); - add(newTagItem); + add(tagAndCommentItem); } + protected Node[] getNodes() { + return nodes; + } + + protected abstract void tagNodes(String tagName, String comment); + private void refreshDirectoryTree() { //TODO instead should send event to node children, which will call its refresh() / refreshKeys() DirectoryTreeTopComponent viewer = DirectoryTreeTopComponent.findInstance();