diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManagerPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManagerPanel.form
new file mode 100755
index 0000000000..572b3b37c8
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManagerPanel.form
@@ -0,0 +1,224 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManagerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManagerPanel.java
new file mode 100755
index 0000000000..dc828dec3f
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManagerPanel.java
@@ -0,0 +1,333 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.sleuthkit.autopsy.casemodule.services;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import javax.swing.DefaultListModel;
+import org.netbeans.spi.options.OptionsPanelController;
+import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+
+/**
+ *
+ * @author smori
+ */
+public class TagsManagerPanel extends javax.swing.JPanel implements OptionsPanel {
+
+ private static final String TAG_NAME_EMPTY = "Tag name text is empty.";
+ private static final String TAG_NAME_ALREADY_EXISTS = "Tag name already exists.";
+ private static final String TAG_NAME_COMMA = "Tag name may not contain commas.";
+
+ 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 final DefaultListModel tagNamesListModel;
+ private final Set tagNames;
+
+ /**
+ * Creates new form TagsManagerPanel
+ */
+ public TagsManagerPanel() {
+ tagNamesListModel = new DefaultListModel<>();
+ tagNamesList.setModel(tagNamesListModel);
+ tagNames = new TreeSet<>();
+
+ initComponents();
+ customizeComponents();
+ }
+
+ private void customizeComponents() {
+ addTagNamesFromTagsSettings();
+
+ }
+
+ private void addTagNamesFromTagsSettings() {
+ String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY);
+ if ((setting != null) && !setting.isEmpty()) {
+ List tagNameTuples = Arrays.asList(setting.split(";"));
+ for (String tagNameTuple : tagNameTuples) {
+ String[] tagNameAttributes = tagNameTuple.split(",");
+ CustomTagName tagName = new CustomTagName(tagNameAttributes[0], tagNameAttributes[1], tagNameAttributes[2]);
+ tagNames.add(tagName);
+ }
+ }
+ }
+
+ private void updateTagNamesListModel() {
+ tagNamesListModel.clear();
+ tagNames.stream().forEach((tagName) -> {
+ tagNamesListModel.addElement(tagName);
+ });
+ }
+
+ /**
+ * 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();
+ jLabel1 = new javax.swing.JLabel();
+ jSplitPane1 = new javax.swing.JSplitPane();
+ jPanel2 = new javax.swing.JPanel();
+ tagNamesListLabel = new javax.swing.JLabel();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ tagNamesList = new javax.swing.JList<>();
+ userTagNameTextField = new javax.swing.JTextField();
+ addTagNameButton = new javax.swing.JButton();
+ deleteTagNameButton = new javax.swing.JButton();
+ tagNameErrLabel = new javax.swing.JLabel();
+ jPanel3 = new javax.swing.JPanel();
+
+ jPanel1.setPreferredSize(new java.awt.Dimension(750, 500));
+
+ org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(TagsManagerPanel.class, "TagsManagerPanel.jLabel1.text")); // NOI18N
+
+ jSplitPane1.setDividerLocation(-100);
+ jSplitPane1.setDividerSize(1);
+
+ org.openide.awt.Mnemonics.setLocalizedText(tagNamesListLabel, org.openide.util.NbBundle.getMessage(TagsManagerPanel.class, "TagsManagerPanel.tagNamesListLabel.text")); // NOI18N
+
+ jScrollPane1.setViewportView(tagNamesList);
+
+ userTagNameTextField.setText(org.openide.util.NbBundle.getMessage(TagsManagerPanel.class, "TagsManagerPanel.userTagNameTextField.text")); // NOI18N
+
+ addTagNameButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(addTagNameButton, org.openide.util.NbBundle.getMessage(TagsManagerPanel.class, "TagsManagerPanel.addTagNameButton.text")); // NOI18N
+ addTagNameButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ addTagNameButtonActionPerformed(evt);
+ }
+ });
+
+ deleteTagNameButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(deleteTagNameButton, org.openide.util.NbBundle.getMessage(TagsManagerPanel.class, "TagsManagerPanel.deleteTagNameButton.text")); // NOI18N
+ deleteTagNameButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ deleteTagNameButtonActionPerformed(evt);
+ }
+ });
+
+ tagNameErrLabel.setForeground(new java.awt.Color(255, 0, 0));
+ org.openide.awt.Mnemonics.setLocalizedText(tagNameErrLabel, org.openide.util.NbBundle.getMessage(TagsManagerPanel.class, "TagsManagerPanel.tagNameErrLabel.text")); // NOI18N
+
+ javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
+ jPanel2.setLayout(jPanel2Layout);
+ jPanel2Layout.setHorizontalGroup(
+ jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(tagNameErrLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(jScrollPane1)
+ .addComponent(tagNamesListLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
+ .addComponent(userTagNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 135, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(addTagNameButton)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(deleteTagNameButton)))
+ .addContainerGap())
+ );
+ jPanel2Layout.setVerticalGroup(
+ jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(tagNamesListLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 374, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(userTagNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(addTagNameButton)
+ .addComponent(deleteTagNameButton))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(tagNameErrLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))
+ );
+
+ jSplitPane1.setLeftComponent(jPanel2);
+
+ javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
+ jPanel3.setLayout(jPanel3Layout);
+ jPanel3Layout.setHorizontalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 330, Short.MAX_VALUE)
+ );
+ jPanel3Layout.setVerticalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 456, Short.MAX_VALUE)
+ );
+
+ jSplitPane1.setRightComponent(jPanel3);
+
+ 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(jLabel1, 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(jLabel1)
+ .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 addTagNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addTagNameButtonActionPerformed
+ String newTagName = userTagNameTextField.getText();
+
+ if (newTagName.isEmpty()) {
+ tagNameErrLabel.setText(TAG_NAME_EMPTY);
+ return;
+ }
+ if (newTagName.contains(",")) {
+ tagNameErrLabel.setText(TAG_NAME_COMMA);
+ return;
+ }
+
+ CustomTagName tagName = new CustomTagName(newTagName, DEFAULT_DESCRIPTION, DEFAULT_COLOR_STRING);
+ boolean added = tagNames.add(tagName);
+ if (!added) {
+ tagNameErrLabel.setText(TAG_NAME_ALREADY_EXISTS);
+ return;
+ }
+
+ updateTagNamesListModel();
+ userTagNameTextField.setText("");
+ tagNameErrLabel.setText("");
+
+ firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+ }//GEN-LAST:event_addTagNameButtonActionPerformed
+
+ private void deleteTagNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTagNameButtonActionPerformed
+ CustomTagName tagName = tagNamesList.getSelectedValue();
+ tagNames.remove(tagName);
+ updateTagNamesListModel();
+
+ if (!tagNamesListModel.isEmpty()) {
+ tagNamesList.setSelectedIndex(0);
+ }
+
+ firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+ }//GEN-LAST:event_deleteTagNameButtonActionPerformed
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton addTagNameButton;
+ private javax.swing.JButton deleteTagNameButton;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JPanel jPanel3;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JSplitPane jSplitPane1;
+ private javax.swing.JLabel tagNameErrLabel;
+ private javax.swing.JList tagNamesList;
+ private javax.swing.JLabel tagNamesListLabel;
+ private javax.swing.JTextField userTagNameTextField;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void store() {
+ if (!tagNames.isEmpty()) {
+ StringBuilder builder = new StringBuilder();
+ for (CustomTagName tagName : tagNames) {
+ if (builder.length() != 0) {
+ builder.append(";");
+ }
+ builder.append(tagName.toString());
+ }
+ ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, builder.toString());
+ }
+ }
+
+ @Override
+ public void load() {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ /**
+ * TagName constructor is not public, CustomTagName is for the purposes of
+ * preserving description and color information that may be in the user tags
+ * settings files.
+ */
+ private class CustomTagName implements Comparable {
+ private final String displayName;
+ private final String description;
+ private final String colorName;
+
+ private CustomTagName(String displayName, String description, String colorName) {
+ this.displayName = displayName;
+ this.description = description;
+ this.colorName = colorName;
+ }
+
+ private String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ public int compareTo(CustomTagName other) {
+ return this.getDisplayName().compareTo(other.getDisplayName());
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 83 * hash + Objects.hashCode(this.displayName);
+ hash = 83 * hash + Objects.hashCode(this.description);
+ hash = 83 * hash + Objects.hashCode(this.colorName);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof CustomTagName)) return false;
+ CustomTagName thatTagName = (CustomTagName) obj;
+ return this.getDisplayName().equals(thatTagName.getDisplayName());
+ }
+
+ @Override
+ public String toString() {
+ return displayName + "," + description + "," + colorName;
+ }
+
+ }
+}