diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 7f4f813495..3d394e9215 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -19,8 +19,8 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; -import java.util.Collections; -import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JMenu; @@ -87,10 +87,12 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Get the current set of tag names. TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); - List tagNames = null; + Map tagNamesMap = null; try { - tagNames = tagsManager.getAllTagNames(); - Collections.sort(tagNames); + tagNamesMap = new TreeMap<>(); + tagNamesMap.putAll(tagsManager.getUserTagNamesMap()); + tagNamesMap.putAll(tagsManager.getPredefinedTagNamesMap()); + tagNamesMap.putAll(tagsManager.getTagNamesInUseMap()); } catch (TskCoreException ex) { Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } @@ -102,11 +104,11 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Each tag name in the current set of tags gets its own menu item in // the "Quick Tags" sub-menu. Selecting one of these menu items adds // a tag with the associated tag name. - if (null != tagNames && !tagNames.isEmpty()) { - for (final TagName tagName : tagNames) { - JMenuItem tagNameItem = new JMenuItem(tagName.getDisplayName()); + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { + for (Map.Entry entry : tagNamesMap.entrySet()) { + JMenuItem tagNameItem = new JMenuItem(entry.getKey()); tagNameItem.addActionListener((ActionEvent e) -> { - addTag(tagName, NO_COMMENT); + getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT); }); quickTagMenu.add(tagNameItem); } @@ -114,7 +116,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); empty.setEnabled(false); quickTagMenu.add(empty); - } + } quickTagMenu.addSeparator(); @@ -143,5 +145,30 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { }); add(tagAndCommentItem); } + + /** + * Method to add to the action listener for each menu item. Allows a tag + * display name to be added to the menu with an action listener without + * having to instantiate a TagName object for it. + * When the method is called, the TagName object is created here if it + * doesn't already exist. + * + * @param tagDisplayName display name for the tag name + * @param tagName TagName object associated with the tag name, + * may be null + * @param comment comment for the content or artifact tag + */ + private void getAndAddTag(String tagDisplayName, TagName tagName, String comment) { + if (tagName == null) { + try { + tagName = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName); + } catch (TagsManager.TagNameAlreadyExistsException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS + } catch (TskCoreException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS + } + } + addTag(tagName, comment); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index d9cc839467..d964d91fc7 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -1,10 +1,10 @@ GetTagNameDialog.tagNameField.text= GetTagNameDialog.cancelButton.text=Cancel GetTagNameDialog.okButton.text=OK -GetTagNameDialog.preexistingLabel.text=Pre-existing Tags: +GetTagNameDialog.preexistingLabel.text=Pre-existing Tag Names: GetTagNameDialog.newTagPanel.border.title=New Tag GetTagNameDialog.tagNameLabel.text=Tag Name: -GetTagNameAndCommentDialog.newTagButton.text=New Tag +GetTagNameAndCommentDialog.newTagButton.text=New Tag Name GetTagNameAndCommentDialog.okButton.text=OK GetTagNameAndCommentDialog.commentText.toolTipText=Enter an optional tag comment or leave blank GetTagNameAndCommentDialog.commentText.text= @@ -42,7 +42,7 @@ GetTagNameDialog.createTag=Create Tag GetTagNameDialog.cancelName=Cancel GetTagNameDialog.mustSupplyTtagName.msg=Must supply a tag name to continue. GetTagNameDialog.tagNameErr=Tag Name -GetTagNameDialog.illegalChars.msg=The tag name contains illegal characters.\nCannot contain any of the following symbols\: \\ \: * ? " < > | +GetTagNameDialog.illegalChars.msg=The tag name contains illegal characters.\nCannot contain any of the following symbols\: \\ \: * ? " < > | , ; GetTagNameDialog.illegalCharsErr=Illegal Characters GetTagNameDialog.unableToAddTagNameToCase.msg=Unable to add the {0} tag name to the case. GetTagNameDialog.taggingErr=Tagging Error @@ -58,4 +58,4 @@ OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder ShowIngestProgressSnapshotAction.actionName.text=Get Ingest Progress Snapshot OpenPythonModulesFolderAction.actionName.text=Python Plugins OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0} -CTL_OpenPythonModulesFolderAction=Python Plugins \ No newline at end of file +CTL_OpenPythonModulesFolderAction=Python Plugins diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form index 47ff059281..17a9738dbd 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form @@ -28,7 +28,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index eb4dbbab6b..fa2b1b1593 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -21,8 +21,8 @@ package org.sleuthkit.autopsy.actions; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; -import java.util.HashMap; -import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ActionMap; @@ -43,7 +43,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private static final long serialVersionUID = 1L; private static final String NO_TAG_NAMES_MESSAGE = NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.noTags"); - private final HashMap tagNames = new HashMap<>(); + private final Map tagNamesMap = new TreeMap<>(); private TagNameAndComment tagNameAndComment = null; public static class TagNameAndComment { @@ -114,19 +114,21 @@ public class GetTagNameAndCommentDialog extends JDialog { // Populate the combo box with the available tag names and save the // tag name DTOs to be enable to return the one the user selects. + // Tag name DTOs may be null (user tag names that have not been used do + // not exist in the database). TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); - List currentTagNames = null; try { - currentTagNames = tagsManager.getAllTagNames(); + tagNamesMap.putAll(tagsManager.getUserTagNamesMap()); + tagNamesMap.putAll(tagsManager.getPredefinedTagNamesMap()); + tagNamesMap.putAll(tagsManager.getTagNamesInUseMap()); } catch (TskCoreException ex) { Logger.getLogger(GetTagNameAndCommentDialog.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - if (null != currentTagNames && currentTagNames.isEmpty()) { + if (null != tagNamesMap && tagNamesMap.isEmpty()) { tagCombo.addItem(NO_TAG_NAMES_MESSAGE); } else { - for (TagName tagName : currentTagNames) { - tagNames.put(tagName.getDisplayName(), tagName); - tagCombo.addItem(tagName.getDisplayName()); + for (String tagDisplayName : tagNamesMap.keySet()) { + tagCombo.addItem(tagDisplayName); } } @@ -197,7 +199,7 @@ public class GetTagNameAndCommentDialog extends JDialog { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(newTagButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 78, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton)) @@ -240,7 +242,18 @@ public class GetTagNameAndCommentDialog extends JDialog { }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - tagNameAndComment = new TagNameAndComment(tagNames.get((String) tagCombo.getSelectedItem()), commentText.getText()); + String tagDisplayName = (String) tagCombo.getSelectedItem(); + TagName tagNameFromCombo = tagNamesMap.get(tagDisplayName); + if (tagNameFromCombo == null) { + try { + tagNameFromCombo = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName); + } catch (TagsManager.TagNameAlreadyExistsException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS + } catch (TskCoreException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS + } + } + tagNameAndComment = new TagNameAndComment(tagNameFromCombo, commentText.getText()); dispose(); }//GEN-LAST:event_okButtonActionPerformed @@ -257,7 +270,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed TagName newTagName = GetTagNameDialog.doDialog(this); if (newTagName != null) { - tagNames.put(newTagName.getDisplayName(), newTagName); + tagNamesMap.put(newTagName.getDisplayName(), newTagName); tagCombo.addItem(newTagName.getDisplayName()); tagCombo.setSelectedItem(newTagName.getDisplayName()); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java index b24f69d757..5386af785a 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java @@ -22,8 +22,9 @@ import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ActionMap; @@ -45,7 +46,7 @@ import org.sleuthkit.datamodel.TskCoreException; public class GetTagNameDialog extends JDialog { private static final String TAG_ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS - private final HashMap tagNames = new HashMap<>(); + private final Map tagNamesMap = new TreeMap<>(); private TagName tagName = null; /** @@ -96,22 +97,16 @@ public class GetTagNameDialog extends JDialog { // Get the current set of tag names and hash them for a speedy lookup in // case the user chooses an existing tag name from the tag names table. TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); - List currentTagNames = null; try { - currentTagNames = tagsManager.getAllTagNames(); + tagNamesMap.putAll(tagsManager.getUserTagNamesMap()); + tagNamesMap.putAll(tagsManager.getPredefinedTagNamesMap()); + tagNamesMap.putAll(tagsManager.getTagNamesInUseMap()); } catch (TskCoreException ex) { Logger.getLogger(GetTagNameDialog.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - if (null != currentTagNames) { - for (TagName name : currentTagNames) { - this.tagNames.put(name.getDisplayName(), name); - } - } else { - currentTagNames = new ArrayList<>(); - } // Populate the tag names table. - tagsTable.setModel(new TagsTableModel(currentTagNames)); + tagsTable.setModel(new TagsTableModel(new ArrayList(tagNamesMap.keySet()))); tagsTable.setTableHeader(null); tagsTable.setCellSelectionEnabled(false); tagsTable.setFocusable(false); @@ -122,30 +117,19 @@ public class GetTagNameDialog extends JDialog { setVisible(true); } - private boolean containsIllegalCharacters(String content) { - return (content.contains("\\") - || content.contains(":") - || content.contains("*") - || content.contains("?") - || content.contains("\"") - || content.contains("<") - || content.contains(">") - || content.contains("|")); - } - private class TagsTableModel extends AbstractTableModel { - private final ArrayList tagNames = new ArrayList<>(); + private final ArrayList tagDisplayNames = new ArrayList<>(); - TagsTableModel(List tagNames) { - for (TagName tagName : tagNames) { - this.tagNames.add(tagName); + TagsTableModel(List tagDisplayNames) { + for (String tagDisplayName : tagDisplayNames) { + this.tagDisplayNames.add(tagDisplayName); } } @Override public int getRowCount() { - return tagNames.size(); + return tagDisplayNames.size(); } @Override @@ -160,7 +144,7 @@ public class GetTagNameDialog extends JDialog { @Override public String getValueAt(int rowIndex, int columnIndex) { - return tagNames.get(rowIndex).getDisplayName(); + return tagDisplayNames.get(rowIndex); } } @@ -305,13 +289,13 @@ public class GetTagNameDialog extends JDialog { "GetTagNameDialog.mustSupplyTtagName.msg"), NbBundle.getMessage(this.getClass(), "GetTagNameDialog.tagNameErr"), JOptionPane.ERROR_MESSAGE); - } else if (containsIllegalCharacters(tagDisplayName)) { + } else if (TagsManager.containsIllegalCharacters(tagDisplayName)) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "GetTagNameDialog.illegalChars.msg"), NbBundle.getMessage(this.getClass(), "GetTagNameDialog.illegalCharsErr"), JOptionPane.ERROR_MESSAGE); } else { - tagName = tagNames.get(tagDisplayName); + tagName = tagNamesMap.get(tagDisplayName); if (tagName == null) { try { tagName = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName); @@ -326,7 +310,7 @@ public class GetTagNameDialog extends JDialog { JOptionPane.ERROR_MESSAGE); tagName = null; } catch (TagsManager.TagNameAlreadyExistsException ex) { - Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "GetTagNameDialog.tagNameAlreadyDef.msg", diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties index 1a3be9454f..ed80e8696e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties @@ -1,3 +1,5 @@ +OptionsCategory_Name_TagNamesOptions=Tags +OptionsCategory_TagNames=TagNames TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} out of content size range (0 - {1}) TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} out of content size range (0 - {1}) TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset @@ -7,3 +9,18 @@ TagsManager.deleteContentTag.noCaseWarning=Failed to publish content tag deleted TagsManager.addBlackboardArtifactTag.noCaseWarning=Failed to publish new blackboard artifact tag event. There is no case open. TagsManager.deleteBlackboardArtifactTag.noCaseWarning=Failed to publish blackboard artifact tag deleted event. There is no case open. Blackboard.unableToIndexArtifact.error.msg=Unable to index blackboard artifact {0} +TagNamesSettingsPanel.deleteTagNameButton.text=Delete Tag Name +TagNamesSettingsPanel.tagNamesListLabel.text=Tag names: +NewUserTagNameDialog.tagNameTextField.text= +NewUserTagNameDialog.newTagNameLabel.text=New Tag Name: +NewUserTagNameDialog.okButton.text=OK +NewUserTagNameDialog.cancelButton.text=Cancel +NewUserTagNameDialog.title.text=New Tag Name +NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ; +NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name +TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.message=The tag name already exists in your settings +TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.title=Tag name already exists +NewUserTagNameDialog.JOptionPane.tagNameEmpty.message=The tag name cannot be empty +NewUserTagNameDialog.JOptionPane.tagNameEmpty.title=Empty tag name +TagNamesSettingsPanel.panelDescriptionLabel.text=Autopsy keeps a list of the tag names you have created in the past. Add more or delete them here. +TagNamesSettingsPanel.newTagNameButton.text=New Tag Name diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/NewUserTagNameDialog.form b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewUserTagNameDialog.form new file mode 100755 index 0000000000..26ab153c27 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewUserTagNameDialog.form @@ -0,0 +1,98 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/NewUserTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewUserTagNameDialog.java new file mode 100755 index 0000000000..bd04678ba9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewUserTagNameDialog.java @@ -0,0 +1,246 @@ +/* +* Autopsy Forensic Browser +* +* Copyright 2011-2016 Basis Technology Corp. +* Contact: carrier sleuthkit org +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.services; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.openide.util.NbBundle; + +class NewUserTagNameDialog extends javax.swing.JDialog { + + private String userTagDisplayName; + private BUTTON_PRESSED result; + + enum BUTTON_PRESSED { + OK, CANCEL; + } + + /** + * Creates a new NewUserTagNameDialog dialog. + */ + NewUserTagNameDialog() { + super(new JFrame(NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.title.text")), + NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.title.text"), true); + initComponents(); + this.display(); + } + + /** + * Sets display settings for the dialog and adds appropriate listeners. + */ + private void display() { + setLayout(new BorderLayout()); + + /* + * Center the dialog + */ + Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); + int width = this.getSize().width; + int height = this.getSize().height; + setLocation((screenDimension.width - width) / 2, (screenDimension.height - height) / 2); + + /* + * Add a handler for when the dialog window is closed directly. + */ + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + doButtonAction(false); + } + }); + + /* + * Add a listener to enable the OK button when the text field changes. + */ + tagNameTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + fire(); + } + @Override + public void removeUpdate(DocumentEvent e) { + fire(); + } + @Override + public void insertUpdate(DocumentEvent e) { + fire(); + } + private void fire() { + enableOkButton(); + } + }); + + enableOkButton(); + + /* + * Used to show the dialog. + */ + setResizable(false); + setVisible(true); + } + + /** + * Called when a button is pressed or when the dialog is closed. + * @param okPressed whether the OK button was pressed. + */ + private void doButtonAction(boolean okPressed) { + if (okPressed) { + String newTagDisplayName = tagNameTextField.getText().trim(); + if (newTagDisplayName.isEmpty()) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameEmpty.message"), + NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameEmpty.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + if (TagsManager.containsIllegalCharacters(newTagDisplayName)) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.message"), + NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + userTagDisplayName = newTagDisplayName; + result = BUTTON_PRESSED.OK; + } else { + result = BUTTON_PRESSED.CANCEL; + } + setVisible(false); + } + + /** + * Returns the tag name entered by the user. + * + * @return a new user tag name + */ + String getTagName() { + return userTagDisplayName; + } + + /** + * Returns information about which button was pressed. + * + * @return BUTTON_PRESSED (OK, CANCEL) + */ + BUTTON_PRESSED getResult() { + return result; + } + + /** + * Enable the OK button if the tag name text field is not empty. + * Sets the enter button as default, so user can press enter to activate + * an okButton press and add the tag name. + */ + private void enableOkButton() { + okButton.setEnabled(!tagNameTextField.getText().isEmpty()); + getRootPane().setDefaultButton(okButton); + } + + /** + * 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 + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + newTagNameLabel = new javax.swing.JLabel(); + tagNameTextField = new javax.swing.JTextField(); + cancelButton = new javax.swing.JButton(); + okButton = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + org.openide.awt.Mnemonics.setLocalizedText(newTagNameLabel, org.openide.util.NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.newTagNameLabel.text")); // NOI18N + + tagNameTextField.setText(org.openide.util.NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.tagNameTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.cancelButton.text")); // NOI18N + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(NewUserTagNameDialog.class, "NewUserTagNameDialog.okButton.text")); // NOI18N + okButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + okButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tagNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 220, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(okButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton)) + .addComponent(newTagNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(newTagNameLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tagNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(50, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cancelButton) + .addComponent(okButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed + doButtonAction(true); + }//GEN-LAST:event_okButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + doButtonAction(false); + }//GEN-LAST:event_cancelButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton cancelButton; + private javax.swing.JLabel newTagNameLabel; + private javax.swing.JButton okButton; + private javax.swing.JTextField tagNameTextField; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesOptionsPanelController.java new file mode 100755 index 0000000000..20c478d966 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesOptionsPanelController.java @@ -0,0 +1,131 @@ +/* +* Autopsy Forensic Browser +* +* Copyright 2011-2016 Basis Technology Corp. +* Contact: carrier sleuthkit org +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.sleuthkit.autopsy.casemodule.services; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +@OptionsPanelController.TopLevelRegistration( + categoryName = "#OptionsCategory_Name_TagNamesOptions", + iconBase = "org/sleuthkit/autopsy/casemodule/services/tags-manager.png", + keywords = "#OptionsCategory_TagNames", + keywordsCategory = "CustomTagNames", + position = 8 +) +public final class TagNamesOptionsPanelController extends OptionsPanelController { + + private TagNamesSettingsPanel panel; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private boolean changed; + + /** + * Component should load its data here. + */ + @Override + public void update() { + getPanel().load(); + changed = false; + } + + /** + * This method is called when both the Ok and Apply buttons are pressed. It + * applies to any of the panels that have been opened in the process of + * using the options pane. + */ + @Override + public void applyChanges() { + if (changed) { + getPanel().store(); + changed = false; + } + } + + /** + * This method is called when the Cancel button is pressed. It applies to + * any of the panels that have been opened in the process of using the + * options pane. + */ + @Override + public void cancel() { + } + + @Override + public boolean isValid() { + return true; + } + + /** + * Used to determine whether any changes have been made to this controller's + * panel. + * + * @return Whether or not a change has been made. + */ + @Override + public boolean isChanged() { + return changed; + } + + @Override + public JComponent getComponent(Lookup lkp) { + return getPanel(); + } + + @Override + public HelpCtx getHelpCtx() { + return null; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + + private TagNamesSettingsPanel getPanel() { + if (panel == null) { + panel = new TagNamesSettingsPanel(); + panel.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) { + changed(); + } + } + }); + } + return panel; + } + + void changed() { + if (!changed) { + changed = true; + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesSettingsPanel.form new file mode 100755 index 0000000000..4412f20274 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesSettingsPanel.form @@ -0,0 +1,203 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesSettingsPanel.java new file mode 100755 index 0000000000..91a24e7ad2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNamesSettingsPanel.java @@ -0,0 +1,316 @@ +/* +* Autopsy Forensic Browser +* +* Copyright 2011-2016 Basis Technology Corp. +* Contact: carrier sleuthkit org +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.sleuthkit.autopsy.casemodule.services; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.DefaultListModel; +import javax.swing.JOptionPane; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; + +/** + * A panel to allow the user to create new tag names or to delete tag names that + * user has created in the past. List of user tag names is maintained in a + * properties file, able to be used across cases. Potentially room to add other + * tag name options in the future. + */ +final class TagNamesSettingsPanel extends javax.swing.JPanel implements OptionsPanel { + + private static final Logger logger = Logger.getLogger(TagNamesSettingsPanel.class.getName()); + + private static final String TAGS_SETTINGS_NAME = "Tags"; //NON-NLS + private static final String TAG_NAMES_SETTING_KEY = "TagNames"; //NON-NLS + + private static final String DEFAULT_DESCRIPTION = ""; + private static final String DEFAULT_COLOR_STRING = "NONE"; + + private DefaultListModel tagNamesListModel; + private List tagNames; + + /** + * Creates new form TagsManagerOptionsPanel + */ + TagNamesSettingsPanel() { + initComponents(); + customizeComponents(); + } + + private void customizeComponents() { + tagNamesListModel = new DefaultListModel<>(); + tagNamesList.setModel(tagNamesListModel); + tagNames = new ArrayList<>(); + tagNamesList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + enableButtons(); + } + }); + } + + /** + * 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 + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + panelDescriptionLabel = new javax.swing.JLabel(); + jSplitPane1 = new javax.swing.JSplitPane(); + modifyTagNameListPanel = new javax.swing.JPanel(); + tagNamesListLabel = new javax.swing.JLabel(); + jScrollPane1 = new javax.swing.JScrollPane(); + tagNamesList = new javax.swing.JList<>(); + newTagNameButton = new javax.swing.JButton(); + deleteTagNameButton = new javax.swing.JButton(); + tagNameAdditionalPanel = new javax.swing.JPanel(); + + jPanel1.setPreferredSize(new java.awt.Dimension(750, 500)); + + org.openide.awt.Mnemonics.setLocalizedText(panelDescriptionLabel, org.openide.util.NbBundle.getMessage(TagNamesSettingsPanel.class, "TagNamesSettingsPanel.panelDescriptionLabel.text")); // NOI18N + + jSplitPane1.setDividerLocation(400); + jSplitPane1.setDividerSize(1); + + org.openide.awt.Mnemonics.setLocalizedText(tagNamesListLabel, org.openide.util.NbBundle.getMessage(TagNamesSettingsPanel.class, "TagNamesSettingsPanel.tagNamesListLabel.text")); // NOI18N + + jScrollPane1.setViewportView(tagNamesList); + + newTagNameButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add-tag.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newTagNameButton, org.openide.util.NbBundle.getMessage(TagNamesSettingsPanel.class, "TagNamesSettingsPanel.newTagNameButton.text")); // NOI18N + newTagNameButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newTagNameButtonActionPerformed(evt); + } + }); + + deleteTagNameButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete-tag.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteTagNameButton, org.openide.util.NbBundle.getMessage(TagNamesSettingsPanel.class, "TagNamesSettingsPanel.deleteTagNameButton.text")); // NOI18N + deleteTagNameButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteTagNameButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout modifyTagNameListPanelLayout = new javax.swing.GroupLayout(modifyTagNameListPanel); + modifyTagNameListPanel.setLayout(modifyTagNameListPanelLayout); + modifyTagNameListPanelLayout.setHorizontalGroup( + modifyTagNameListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(modifyTagNameListPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(modifyTagNameListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tagNamesListLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(modifyTagNameListPanelLayout.createSequentialGroup() + .addComponent(newTagNameButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deleteTagNameButton) + .addGap(0, 113, Short.MAX_VALUE)) + .addComponent(jScrollPane1)) + .addContainerGap()) + ); + modifyTagNameListPanelLayout.setVerticalGroup( + modifyTagNameListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(modifyTagNameListPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(tagNamesListLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 383, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(modifyTagNameListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(newTagNameButton) + .addComponent(deleteTagNameButton)) + .addContainerGap()) + ); + + jSplitPane1.setLeftComponent(modifyTagNameListPanel); + + javax.swing.GroupLayout tagNameAdditionalPanelLayout = new javax.swing.GroupLayout(tagNameAdditionalPanel); + tagNameAdditionalPanel.setLayout(tagNameAdditionalPanelLayout); + tagNameAdditionalPanelLayout.setHorizontalGroup( + tagNameAdditionalPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 356, Short.MAX_VALUE) + ); + tagNameAdditionalPanelLayout.setVerticalGroup( + tagNameAdditionalPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 456, Short.MAX_VALUE) + ); + + jSplitPane1.setRightComponent(tagNameAdditionalPanel); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jSplitPane1) + .addComponent(panelDescriptionLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(panelDescriptionLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSplitPane1) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 778, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void newTagNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagNameButtonActionPerformed + NewUserTagNameDialog dialog = new NewUserTagNameDialog(); + NewUserTagNameDialog.BUTTON_PRESSED result = dialog.getResult(); + if (result == NewUserTagNameDialog.BUTTON_PRESSED.OK) { + String newTagDisplayName = dialog.getTagName(); + UserTagName newTagName = new UserTagName(newTagDisplayName, DEFAULT_DESCRIPTION, DEFAULT_COLOR_STRING); + /* + * If tag name already exists, don't add the tag name. + */ + if (tagNames.contains(newTagName)) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(TagNamesSettingsPanel.class, "TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.message"), + NbBundle.getMessage(TagNamesSettingsPanel.class, "TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.title"), + JOptionPane.INFORMATION_MESSAGE); + } else { + tagNames.add(newTagName); + updateTagNamesListModel(); + /* + * Set the selection to the tag name that was just added. + */ + int index = tagNames.indexOf(newTagName); + tagNamesList.setSelectedIndex(index); + enableButtons(); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + } + }//GEN-LAST:event_newTagNameButtonActionPerformed + + private void deleteTagNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTagNameButtonActionPerformed + UserTagName tagName = tagNamesList.getSelectedValue(); + tagNames.remove(tagName); + updateTagNamesListModel(); + enableButtons(); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_deleteTagNameButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton deleteTagNameButton; + private javax.swing.JPanel jPanel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JSplitPane jSplitPane1; + private javax.swing.JPanel modifyTagNameListPanel; + private javax.swing.JButton newTagNameButton; + private javax.swing.JLabel panelDescriptionLabel; + private javax.swing.JPanel tagNameAdditionalPanel; + private javax.swing.JList tagNamesList; + private javax.swing.JLabel tagNamesListLabel; + // End of variables declaration//GEN-END:variables + + /** + * Updates the tag names model for the tag names list component. + */ + private void updateTagNamesListModel() { + tagNamesListModel.clear(); + Set tagNameSet = new HashSet<>(); + tagNameSet.addAll(tagNames); + tagNames.clear(); + tagNames.addAll(tagNameSet); + Collections.sort(tagNames); + for (UserTagName tagName : tagNames) { + tagNamesListModel.addElement(tagName); + } + } + + /** + * Stores tag name changes in the properties file, called when OK or Apply + * is selected in the options panel. + * + * Adds all new tag names to the case database for displaying usable tag + * names when tagging. + */ + @Override + public void store() { + StringBuilder setting = new StringBuilder(); + for (UserTagName tagName : tagNames) { + if (setting.length() != 0) { + setting.append(";"); + } + setting.append(tagName.toSettingsFormat()); + } + ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting.toString()); + if (Case.isCaseOpen()) { + Case.getCurrentCase().getServices().getTagsManager().storeNewUserTagNames(tagNames); + } + } + + /** + * Updates the tag names list component with tag names from the properties + * file. + */ + @Override + public void load() { + String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY); + tagNames.clear(); + if (null != setting && !setting.isEmpty()) { + List tagNameTuples = Arrays.asList(setting.split(";")); + for (String tagNameTuple : tagNameTuples) { + String[] tagNameAttributes = tagNameTuple.split(","); + tagNames.add(new UserTagName(tagNameAttributes[0], tagNameAttributes[1], tagNameAttributes[2])); + } + } + updateTagNamesListModel(); + enableButtons(); + } + + /** + * Only enable delete button when there is a tag name selected in the list. + */ + private void enableButtons() { + boolean ruleIsSelected = tagNamesList.getSelectedIndex() != -1; + deleteTagNameButton.setEnabled(ruleIsSelected); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index e8d61bdeba..7798d3cf3a 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -1,28 +1,32 @@ /* - * Autopsy Forensic Browser - * - * Copyright 2011-2016 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +* Autopsy Forensic Browser +* +* Copyright 2011-2016 Basis Technology Corp. +* Contact: carrier sleuthkit org +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. */ package org.sleuthkit.autopsy.casemodule.services; import java.io.Closeable; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; 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 org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; @@ -53,7 +57,7 @@ public class TagsManager implements Closeable { * Constructs a per case Autopsy service that manages the creation, * updating, and deletion of tags applied to content and blackboard * artifacts by users. - * + * * @param caseDb The case database. */ TagsManager(SleuthkitCase caseDb) { @@ -61,8 +65,7 @@ public class TagsManager implements Closeable { } /** - * Gets a list of all tag names currently available for tagging content or - * artifacts. + * Gets a list of all tag names currently in the case database. * * @return A list, possibly empty, of TagName data transfer objects (DTOs). * @@ -77,6 +80,58 @@ public class TagsManager implements Closeable { return caseDb.getAllTagNames(); } + /** + * Gets a mapping of user tag name display names to TagName DTOs if they + * have been added to the database. Otherwise, the display name maps to + * null. + * + * @return A map of String display name to TagName DTO, TagName may be null + */ + public synchronized Map getUserTagNamesMap() { + lazyLoadExistingTagNames(); + Map tagNamesMap = new HashMap<>(); + String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY); + if (null != setting && !setting.isEmpty()) { + List tagNameTuples = Arrays.asList(setting.split(";")); + for (String tagNameTuple : tagNameTuples) { + String[] tagNameAttributes = tagNameTuple.split(","); + tagNamesMap.put(tagNameAttributes[0], uniqueTagNames.get(tagNameAttributes[0])); + } + } + return tagNamesMap; + } + + /** + * Gets a mapping of predefined tag names to their TagName DTOs. Currently + * only for the bookmark tag. + * + * @return A map of String display name to TagName DTO + */ + public synchronized Map getPredefinedTagNamesMap() { + Map tagNamesMap = new HashMap<>(); + TagName bookmarkTagName = uniqueTagNames.get(NbBundle.getMessage(this.getClass(), "TagsManager.predefTagNames.bookmark.text")); + tagNamesMap.put(NbBundle.getMessage(this.getClass(), "TagsManager.predefTagNames.bookmark.text"), bookmarkTagName); + return tagNamesMap; + } + + /** + * Gets a mapping of the display names of predefined tag names and tag names + * that are in use to their TagName DTOs. + * + * @return A map of String display name to TagName DTO + * + * @throws TskCoreException If there is an error reading from the case + * database. + */ + public synchronized Map getTagNamesInUseMap() throws TskCoreException { + List tagNames = getTagNamesInUse(); + Map tagNamesMap = new HashMap<>(); + for (TagName tagName : tagNames) { + tagNamesMap.put(tagName.getDisplayName(), tagName); + } + return tagNamesMap; + } + /** * Gets a list of all tag names currently in use for tagging content or * artifacts. @@ -103,7 +158,8 @@ public class TagsManager implements Closeable { */ public synchronized boolean tagNameExists(String tagDisplayName) { lazyLoadExistingTagNames(); - return uniqueTagNames.containsKey(tagDisplayName); + return uniqueTagNames.containsKey(tagDisplayName) && + (uniqueTagNames.get(tagDisplayName) != null); } /** @@ -166,21 +222,31 @@ public class TagsManager implements Closeable { if (null == caseDb) { throw new TskCoreException("Tags manager has been closed"); } + lazyLoadExistingTagNames(); + + /* + * It is possible user is trying to add back a tag after having deleted + * it. It is also possible a user tag name was never added to the + * database. + */ if (uniqueTagNames.containsKey(displayName)) { - throw new TagNameAlreadyExistsException(); + if (uniqueTagNames.get(displayName) != null) { + TagName existingTagName = uniqueTagNames.get(displayName); + long count = getContentTagsCountByTagName(existingTagName) + getBlackboardArtifactTagsCountByTagName(existingTagName); + if (count == 0) { + addNewTagNameToTagsSettings(existingTagName); + return existingTagName; + } else { + throw new TagNameAlreadyExistsException(); + } + } } - - /* - * Add the tag name to the case. - */ + TagName newTagName = caseDb.addTagName(displayName, description, color); - - /* - * Add the tag name to the tags settings. - */ uniqueTagNames.put(newTagName.getDisplayName(), newTagName); - saveTagNamesToTagsSettings(); + + addNewTagNameToTagsSettings(newTagName); return newTagName; } @@ -563,7 +629,7 @@ public class TagsManager implements Closeable { } /** - * Closes the tags manager, saving the avaialble tag names to secondary + * Closes the tags manager, saving the available tag names to secondary * storage. * * @throws IOException If there is a problem closing the tags manager. @@ -572,7 +638,6 @@ public class TagsManager implements Closeable { @Override @Deprecated public synchronized void close() throws IOException { - saveTagNamesToTagsSettings(); caseDb = null; } @@ -583,10 +648,12 @@ public class TagsManager implements Closeable { private void lazyLoadExistingTagNames() { if (!tagNamesLoaded) { addTagNamesFromCurrentCase(); - addTagNamesFromTagsSettings(); addPredefinedTagNames(); - saveTagNamesToTagsSettings(); + addTagNamesFromTagsSettings(); tagNamesLoaded = true; + } else { + // Reload case db tag names in case another user has added some. + addTagNamesFromCurrentCase(); } } @@ -621,12 +688,7 @@ public class TagsManager implements Closeable { for (String tagNameTuple : tagNameTuples) { String[] tagNameAttributes = tagNameTuple.split(","); if (!uniqueTagNames.containsKey(tagNameAttributes[0])) { - try { - TagName tagName = caseDb.addTagName(tagNameAttributes[0], tagNameAttributes[1], TagName.HTML_COLOR.getColorByName(tagNameAttributes[2])); - uniqueTagNames.put(tagName.getDisplayName(), tagName); - } catch (TskCoreException ex) { - Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to add saved tag name " + tagNameAttributes[0], ex); //NON-NLS - } + uniqueTagNames.put(tagNameAttributes[0], null); } } } @@ -648,24 +710,55 @@ public class TagsManager implements Closeable { } /** - * Saves the tag names to a properties file. The properties file is used to - * make it possible to use tag names across cases. + * Adds any user defined tag name to the case db and also to uniqueTagNames, + * to allow user tag names to be displayed while tagging. + * + * @param userTagNames a List of UserTagName objects to be potentially added */ - private void saveTagNamesToTagsSettings() { - if (!uniqueTagNames.isEmpty()) { - StringBuilder setting = new StringBuilder(); - for (TagName tagName : uniqueTagNames.values()) { - if (setting.length() != 0) { - setting.append(";"); - } - setting.append(tagName.getDisplayName()).append(","); - setting.append(tagName.getDescription()).append(","); - setting.append(tagName.getColor().name()); + void storeNewUserTagNames(List userTagNames) { + lazyLoadExistingTagNames(); + for (UserTagName utn : userTagNames) { + if (!uniqueTagNames.containsKey(utn.getDisplayName())) { + uniqueTagNames.put(utn.getDisplayName(), null); } - ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting.toString()); } } + /** + * Adds a new tag name to the settings file, used when user creates a new + * tag name. + */ + private void addNewTagNameToTagsSettings(TagName tagName) { + String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY); + if (setting == null || setting.isEmpty()) { + setting = ""; + } else { + setting += ";"; + } + setting += tagName.getDisplayName() + "," + tagName.getDescription() + "," + tagName.getColor().toString(); + ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting); + } + + /** + * Returns true if the tag display name contains an illegal character. Used + * after a tag display name is retrieved from user input. + * + * @param content Display name of the tag being added. + * @return boolean indicating whether the name has an invalid character. + */ + public static boolean containsIllegalCharacters(String content) { + return (content.contains("\\") + || content.contains(":") + || content.contains("*") + || content.contains("?") + || content.contains("\"") + || content.contains("<") + || content.contains(">") + || content.contains("|") + || content.contains(",") + || content.contains(";")); + } + /** * Exception thrown if there is an attempt to add a duplicate tag name. */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/UserTagName.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/UserTagName.java new file mode 100755 index 0000000000..c761665c08 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/UserTagName.java @@ -0,0 +1,86 @@ +/* +* Autopsy Forensic Browser +* +* Copyright 2011-2016 Basis Technology Corp. +* Contact: carrier sleuthkit org +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.sleuthkit.autopsy.casemodule.services; + +import java.util.Objects; + +/** + * Because the DTO TagName constructor can not be called outside of its class + * package, UserTagName is used to keep track of user tag names while + * preserving properties that will potentially be implemented in the future + * (tag name description and tag name color). + */ +class UserTagName implements Comparable { + + private final String displayName; + private final String description; + private final String colorName; + + UserTagName(String displayName, String description, String colorName) { + this.displayName = displayName; + this.description = description; + this.colorName = colorName; + } + + String getDisplayName() { + return displayName; + } + + String getDescription() { + return description; + } + + String getColorName() { + return colorName; + } + + @Override + public int compareTo(UserTagName other) { + return this.getDisplayName().toLowerCase().compareTo(other.getDisplayName().toLowerCase()); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + Objects.hashCode(this.displayName); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof UserTagName)) { + return false; + } + UserTagName thatTagName = (UserTagName) obj; + return this.getDisplayName().equals(thatTagName.getDisplayName()); + } + + @Override + public String toString() { + return displayName; + } + + /** + * @return A string representation of the tag name in the format that is + * used by the properties file. + */ + public String toSettingsFormat() { + return displayName + "," + description + "," + colorName; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/tags-manager.png b/Core/src/org/sleuthkit/autopsy/casemodule/services/tags-manager.png new file mode 100755 index 0000000000..b782cfe010 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/casemodule/services/tags-manager.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/add-tag.png b/Core/src/org/sleuthkit/autopsy/images/add-tag.png new file mode 100755 index 0000000000..e60e692bac Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/add-tag.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/delete-tag.png b/Core/src/org/sleuthkit/autopsy/images/delete-tag.png new file mode 100755 index 0000000000..25edd1ab6b Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/delete-tag.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/edit-tag.png b/Core/src/org/sleuthkit/autopsy/images/edit-tag.png new file mode 100755 index 0000000000..4bc2a7ec13 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/edit-tag.png differ