diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactListPanel.java new file mode 100644 index 0000000000..8380d39a6d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactListPanel.java @@ -0,0 +1,122 @@ +/* + * Autopsy + * + * Copyright 2020 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.discovery.ui; + +import java.awt.Point; +import java.util.List; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.event.ListSelectionListener; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Abstract class to define methods expected of a list of artifacts in the + * discovery details section. + */ +abstract class AbstractArtifactListPanel extends JPanel { + + private static final long serialVersionUID = 1L; + + /** + * Add a listener to the table of artifacts to perform actions when an + * artifact is selected. + * + * @param listener The listener to add to the table of artifacts. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract void addMouseListener(java.awt.event.MouseAdapter mouseListener); + + /** + * Display the specified JPopupMenu at the specified point. + * + * @param popupMenu The JPopupMenu to display. + * @param point The point the menu should be displayed at. + */ + abstract void showPopupMenu(JPopupMenu popupMenu, Point point); + + /** + * The artifact which is currently selected, null if no artifact is + * selected. + * + * @return The currently selected BlackboardArtifact or null if none is + * selected. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract BlackboardArtifact getSelectedArtifact(); + + /** + * Select the row at the specified point. + * + * @param point The point which if a row exists it should be selected. + * + * @return True if a row was selected, false if no row was selected. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract boolean selectAtPoint(Point point); + + /** + * Add the specified list of artifacts to the list of artifacts which should + * be displayed. + * + * @param artifactList The list of artifacts to display. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract void addArtifacts(List artifactList); + + /** + * Remove a listener from the table of artifacts. + * + * @param listener The listener to remove from the table of artifacts. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract void removeSelectionListener(ListSelectionListener listener); + + /** + * Add a listener to the table of artifacts to perform actions when an + * artifact is selected. + * + * @param listener The listener to add to the table of artifacts. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract void addSelectionListener(ListSelectionListener listener); + + /** + * Select the first available artifact in the list if it is not empty to + * populate the panel to the right. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract void selectFirst(); + + /** + * Remove all artifacts from the list of artifacts displayed. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract void clearList(); + + /** + * Whether the list of artifacts is empty. + * + * @return true if the list of artifacts is empty, false if there are + * artifacts. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract boolean isEmpty(); + +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactMenuMouseAdapter.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactMenuMouseAdapter.java new file mode 100644 index 0000000000..f4b7b25954 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactMenuMouseAdapter.java @@ -0,0 +1,219 @@ +/* + * Autopsy + * + * Copyright 2020 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.discovery.ui; + +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JSeparator; +import javax.swing.SwingUtilities; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.actionhelpers.ExtractActionHelper; +import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * MouseAdapter to display a context menu on the specified + * AbstractArtifactListPanel when the user right clicks. + */ +class ArtifactMenuMouseAdapter extends java.awt.event.MouseAdapter { + + private final AbstractArtifactListPanel listPanel; + private static final Logger logger = Logger.getLogger(ArtifactMenuMouseAdapter.class.getName()); + + /** + * Create a new ArtifactMenMouseAdapter. + * + * @param listPanel The panel which the menu should be in regards to. + */ + ArtifactMenuMouseAdapter(AbstractArtifactListPanel listPanel) { + this.listPanel = listPanel; + } + + @Override + public void mouseClicked(java.awt.event.MouseEvent evt) { + if (!evt.isPopupTrigger() && SwingUtilities.isRightMouseButton(evt) && listPanel != null && !listPanel.isEmpty()) { + if (listPanel.selectAtPoint(evt.getPoint())) { + showPopupMenu(evt); + } + } + } + + /** + * If an artifact is selected display a JPopupMenu which has available + * actions. + * + * @param event The mouseEvent being responded to. + */ + private void showPopupMenu(java.awt.event.MouseEvent event) { + BlackboardArtifact artifact = listPanel.getSelectedArtifact(); + if (artifact == null) { + return; + } + try { + JMenuItem[] items = getMenuItems(artifact); + JPopupMenu popupMenu = new JPopupMenu(); + for (JMenuItem menu : items) { + if (menu != null) { + popupMenu.add(menu); + } else { + popupMenu.add(new JSeparator()); + } + } + listPanel.showPopupMenu(popupMenu, event.getPoint()); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get source content of artifact with ID: " + artifact.getArtifactID(), ex); + } + } + + /** + * Returns a list of JMenuItems for the artifact. The list list may contain + * nulls which should be removed or replaced with JSeparators. + * + * @param artifact The artifact to get menu items for. + * + * @return List of menu items. + * + * @throws TskCoreException + */ + private JMenuItem[] getMenuItems(BlackboardArtifact artifact) throws TskCoreException { + List menuItems = new ArrayList<>(); + BlackboardAttribute pathIdAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID)); + long contentId; + if (pathIdAttr != null) { + contentId = pathIdAttr.getValueLong(); + } else { + contentId = artifact.getObjectID(); + } + Content content = artifact.getSleuthkitCase().getContentById(contentId); + menuItems.addAll(getTimelineMenuItems(artifact)); + menuItems.addAll(getDataModelActionFactoryMenuItems(artifact, content)); + menuItems.add(DeleteFileContentTagAction.getInstance().getMenuForFiles(Arrays.asList((AbstractFile) content))); + menuItems.add(DeleteFileBlackboardArtifactTagAction.getInstance().getMenuForArtifacts(Arrays.asList(artifact))); + return menuItems.toArray(new JMenuItem[0]); + } + + /** + * Gets the Timeline Menu Items for this artifact. + * + * @param artifact The artifact to get menu items for. + * + * @return List of timeline menu items. + */ + private List getTimelineMenuItems(BlackboardArtifact artifact) { + List menuItems = new ArrayList<>(); + //if this artifact has a time stamp add the action to view it in the timeline + try { + if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact)) { + menuItems.add(new JMenuItem(new ViewArtifactInTimelineAction(artifact))); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting arttribute(s) from blackboard artifact %d.", artifact.getArtifactID()), ex); //NON-NLS + } + + return menuItems; + } + + /** + * Use the DateModelActionsFactory to get some of the basic actions for the + * artifact. The advantage to using the DataModelActionsFactory is that the + * menu items can be put in a consistent order with other parts of the UI. + * + * @param artifact The artifact to get menu items for. + * @param content The file the artifact is in regards to. + * + * @return List of JMenuItems for the DataModelActionFactory actions. + */ + @NbBundle.Messages({ + "ArtifactMenuMouseAdapter_ExternalViewer_label=Open in external viewer" + }) + private List getDataModelActionFactoryMenuItems(BlackboardArtifact artifact, Content content) { + List menuItems = new ArrayList<>(); + List actions = DataModelActionsFactory.getActions(content, true); + for (Action action : actions) { + if (action == null) { + menuItems.add(null); + } else if (action instanceof ExportCSVAction) { + // Do nothing we don't need this menu item. + } else if (action instanceof AddContentTagAction) { + menuItems.add(((AddContentTagAction) action).getMenuForContent(Arrays.asList((AbstractFile) content))); + } else if (action instanceof AddBlackboardArtifactTagAction) { + menuItems.add(((AddBlackboardArtifactTagAction) action).getMenuForContent(Arrays.asList(artifact))); + } else if (action instanceof ExternalViewerShortcutAction) { + // Replace with an ExternalViewerAction + ExternalViewerAction newAction = new ExternalViewerAction(Bundle.ArtifactMenuMouseAdapter_ExternalViewer_label(), new FileNode((AbstractFile) content)); + menuItems.add(new JMenuItem(newAction)); + } else if (action instanceof ExtractAction) { + menuItems.add(new JMenuItem(new ExtractFileAction((AbstractFile) content))); + } else { + menuItems.add(new JMenuItem(action)); + } + } + return menuItems; + } + + /** + * An action class for extracting the related file. + */ + @NbBundle.Messages({ + "ArtifactMenuMouseAdapter_label=Extract Files" + }) + private final class ExtractFileAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + final private AbstractFile file; + + /** + * Construct a new ExtractFileAction. + * + * @param file The AbstractFile to be extracted. + */ + private ExtractFileAction(AbstractFile file) { + super(Bundle.ArtifactMenuMouseAdapter_label()); + this.file = file; + } + + @Override + public void actionPerformed(ActionEvent e) { + ExtractActionHelper helper = new ExtractActionHelper(); + helper.extract(e, Arrays.asList(file)); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form index e04c0f37d9..3b9f9f4c7a 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form @@ -48,7 +48,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java index 9aa41b3c20..19520819c6 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java @@ -18,10 +18,11 @@ */ package org.sleuthkit.autopsy.discovery.ui; +import java.awt.Point; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; -import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import org.apache.commons.io.FilenameUtils; @@ -38,11 +39,11 @@ import org.sleuthkit.datamodel.TskCoreException; * Panel to display list of artifacts for selected domain. * */ -class ArtifactsListPanel extends JPanel { +final class ArtifactsListPanel extends AbstractArtifactListPanel { private static final long serialVersionUID = 1L; - private final DomainArtifactTableModel tableModel; private static final Logger logger = Logger.getLogger(ArtifactsListPanel.class.getName()); + private final DomainArtifactTableModel tableModel; /** * Creates new form ArtifactsListPanel. @@ -53,68 +54,62 @@ class ArtifactsListPanel extends JPanel { ArtifactsListPanel(BlackboardArtifact.ARTIFACT_TYPE artifactType) { tableModel = new DomainArtifactTableModel(artifactType); initComponents(); - jTable1.getRowSorter().toggleSortOrder(0); - jTable1.getRowSorter().toggleSortOrder(0); + artifactsTable.getRowSorter().toggleSortOrder(0); + artifactsTable.getRowSorter().toggleSortOrder(0); } - /** - * Add a listener to the table of artifacts to perform actions when an - * artifact is selected. - * - * @param listener The listener to add to the table of artifacts. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + void addMouseListener(java.awt.event.MouseAdapter mouseListener) { + artifactsTable.addMouseListener(mouseListener); + } + + @Override + void showPopupMenu(JPopupMenu popupMenu, Point point) { + popupMenu.show(artifactsTable, point.x, point.y); + } + + @Override void addSelectionListener(ListSelectionListener listener) { - jTable1.getSelectionModel().addListSelectionListener(listener); + artifactsTable.getSelectionModel().addListSelectionListener(listener); } - /** - * Remove a listener from the table of artifacts. - * - * @param listener The listener to remove from the table of artifacts. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - void removeListSelectionListener(ListSelectionListener listener) { - jTable1.getSelectionModel().removeListSelectionListener(listener); + @Override + void removeSelectionListener(ListSelectionListener listener) { + artifactsTable.getSelectionModel().removeListSelectionListener(listener); } - /** - * The artifact which is currently selected, null if no artifact is - * selected. - * - * @return The currently selected BlackboardArtifact or null if none is - * selected. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + boolean selectAtPoint(Point point) { + boolean pointSelected = false; + int row = artifactsTable.rowAtPoint(point); + if (row < artifactsTable.getRowCount() && row >= 0) { + artifactsTable.clearSelection(); + artifactsTable.addRowSelectionInterval(row, row); + pointSelected = true; + } + return pointSelected; + } + + @Override BlackboardArtifact getSelectedArtifact() { - int selectedIndex = jTable1.getSelectionModel().getLeadSelectionIndex(); - if (selectedIndex < jTable1.getSelectionModel().getMinSelectionIndex() || jTable1.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > jTable1.getSelectionModel().getMaxSelectionIndex()) { + int selectedIndex = artifactsTable.getSelectionModel().getLeadSelectionIndex(); + if (selectedIndex < artifactsTable.getSelectionModel().getMinSelectionIndex() || artifactsTable.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > artifactsTable.getSelectionModel().getMaxSelectionIndex()) { return null; } - return tableModel.getArtifactByRow(jTable1.convertRowIndexToModel(selectedIndex)); + return tableModel.getArtifactByRow(artifactsTable.convertRowIndexToModel(selectedIndex)); } - /** - * Whether the list of artifacts is empty. - * - * @return true if the list of artifacts is empty, false if there are - * artifacts. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override boolean isEmpty() { return tableModel.getRowCount() <= 0; } - /** - * Select the first available artifact in the list if it is not empty to - * populate the panel to the right. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override void selectFirst() { if (!isEmpty()) { - jTable1.setRowSelectionInterval(0, 0); + artifactsTable.setRowSelectionInterval(0, 0); } else { - jTable1.clearSelection(); + artifactsTable.clearSelection(); } } @@ -125,10 +120,11 @@ class ArtifactsListPanel extends JPanel { * @param artifactList The list of artifacts to display. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override void addArtifacts(List artifactList) { tableModel.setContents(artifactList); - jTable1.validate(); - jTable1.repaint(); + artifactsTable.validate(); + artifactsTable.repaint(); tableModel.fireTableDataChanged(); } @@ -136,7 +132,8 @@ class ArtifactsListPanel extends JPanel { * Remove all artifacts from the list of artifacts displayed. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - void clearArtifacts() { + @Override + void clearList() { tableModel.setContents(new ArrayList<>()); tableModel.fireTableDataChanged(); } @@ -150,19 +147,18 @@ class ArtifactsListPanel extends JPanel { private void initComponents() { javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane(); - jTable1 = new javax.swing.JTable(); + artifactsTable = new javax.swing.JTable(); setOpaque(false); setPreferredSize(new java.awt.Dimension(300, 0)); jScrollPane1.setBorder(null); jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 0)); - jScrollPane1.setPreferredSize(new java.awt.Dimension(0, 0)); - jTable1.setAutoCreateRowSorter(true); - jTable1.setModel(tableModel); - jTable1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - jScrollPane1.setViewportView(jTable1); + artifactsTable.setAutoCreateRowSorter(true); + artifactsTable.setModel(tableModel); + artifactsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(artifactsTable); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -172,7 +168,7 @@ class ArtifactsListPanel extends JPanel { ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 607, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -204,7 +200,7 @@ class ArtifactsListPanel extends JPanel { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void setContents(List artifacts) { - jTable1.clearSelection(); + artifactsTable.clearSelection(); artifactList.clear(); artifactList.addAll(artifacts); } @@ -347,6 +343,6 @@ class ArtifactsListPanel extends JPanel { } } // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTable jTable1; + private javax.swing.JTable artifactsTable; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED index 647dca24d5..d2be24b28f 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED @@ -1,3 +1,5 @@ +ArtifactMenuMouseAdapter_ExternalViewer_label=Open in external viewer +ArtifactMenuMouseAdapter_label=Extract Files ArtifactsListPanel.dateColumn.name=Date/Time ArtifactsListPanel.fileNameColumn.name=Name ArtifactsListPanel.mimeTypeColumn.name=MIME Type diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java index 98ced893df..299e1285df 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java @@ -27,6 +27,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.stream.Collectors; +import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import javax.swing.plaf.basic.BasicSplitPaneDivider; @@ -100,6 +101,7 @@ public final class DiscoveryTopComponent extends TopComponent { } }); rightSplitPane.setTopComponent(resultsPanel); + rightSplitPane.setBottomComponent(new JPanel()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java index d8c33329fb..1bc31e1d9b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java @@ -63,6 +63,7 @@ final class DomainArtifactsTabPanel extends JPanel { initComponents(); this.artifactType = type; listPanel = new ArtifactsListPanel(artifactType); + listPanel.addMouseListener(new ArtifactMenuMouseAdapter(listPanel)); jSplitPane1.setLeftComponent(listPanel); setRightComponent(); listPanel.addSelectionListener(listener); @@ -113,7 +114,7 @@ final class DomainArtifactsTabPanel extends JPanel { void setStatus(ArtifactRetrievalStatus status) { this.status = status; if (status == ArtifactRetrievalStatus.UNPOPULATED) { - listPanel.clearArtifacts(); + listPanel.clearList(); if (rightPanel != null){ rightPanel.setArtifact(null); } @@ -131,7 +132,7 @@ final class DomainArtifactsTabPanel extends JPanel { void handleArtifactSearchResultEvent(DiscoveryEventUtils.ArtifactSearchResultEvent artifactresultEvent) { if (artifactType == artifactresultEvent.getArtifactType()) { SwingUtilities.invokeLater(() -> { - listPanel.removeListSelectionListener(listener); + listPanel.removeSelectionListener(listener); listPanel.addArtifacts(artifactresultEvent.getListOfArtifacts()); status = ArtifactRetrievalStatus.POPULATED; setEnabled(!listPanel.isEmpty()); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java index 348591e735..01f3d67183 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java @@ -18,10 +18,11 @@ */ package org.sleuthkit.autopsy.discovery.ui; +import java.awt.Point; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; -import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import org.apache.commons.lang.StringUtils; @@ -36,7 +37,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Panel to display list of artifacts types and descriptions. */ -class MiniTimelineArtifactListPanel extends JPanel { +class MiniTimelineArtifactListPanel extends AbstractArtifactListPanel { private static final long serialVersionUID = 1L; private final TypeDescriptionTableModel tableModel; @@ -53,91 +54,76 @@ class MiniTimelineArtifactListPanel extends JPanel { MiniTimelineArtifactListPanel() { tableModel = new TypeDescriptionTableModel(); initComponents(); - jTable1.getRowSorter().toggleSortOrder(0); - jTable1.getRowSorter().toggleSortOrder(0); + artifactsTable.getRowSorter().toggleSortOrder(0); + artifactsTable.getRowSorter().toggleSortOrder(0); } - /** - * Add a listener to the table of artifacts to perform actions when a - * artifact is selected. - * - * @param listener The listener to add to the table of artifacts. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + void addMouseListener(java.awt.event.MouseAdapter mouseListener) { + artifactsTable.addMouseListener(mouseListener); + } + + @Override + void showPopupMenu(JPopupMenu popupMenu, Point point) { + popupMenu.show(artifactsTable, point.x, point.y); + } + + @Override void addSelectionListener(ListSelectionListener listener) { - jTable1.getSelectionModel().addListSelectionListener(listener); + artifactsTable.getSelectionModel().addListSelectionListener(listener); } - /** - * Remove a listener from the table of artifacts. - * - * @param listener The listener to remove from the table of artifacts. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - void removeListSelectionListener(ListSelectionListener listener) { - jTable1.getSelectionModel().removeListSelectionListener(listener); + @Override + void removeSelectionListener(ListSelectionListener listener) { + artifactsTable.getSelectionModel().removeListSelectionListener(listener); } - /** - * Whether the list of artifacts is empty. - * - * @return True if the list of artifacts is empty, false if there are - * artifacts. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override boolean isEmpty() { return tableModel.getRowCount() <= 0; } - /** - * Select the first available artifact in the list if it is not empty to - * populate the list to the right. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override void selectFirst() { if (!isEmpty()) { - jTable1.setRowSelectionInterval(0, 0); + artifactsTable.setRowSelectionInterval(0, 0); } else { - jTable1.clearSelection(); + artifactsTable.clearSelection(); } } - /** - * The artifact which is currently selected, null if no artifact is - * selected. - * - * @return The currently selected BlackboardArtifact or null if none is - * selected. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + boolean selectAtPoint(Point point) { + boolean pointSelected = false; + int row = artifactsTable.rowAtPoint(point); + artifactsTable.clearSelection(); + if (row < artifactsTable.getRowCount() && row >= 0) { + artifactsTable.addRowSelectionInterval(row, row); + pointSelected = true; + } + return pointSelected; + } + + @Override BlackboardArtifact getSelectedArtifact() { - int selectedIndex = jTable1.getSelectionModel().getLeadSelectionIndex(); - if (selectedIndex < jTable1.getSelectionModel().getMinSelectionIndex() - || jTable1.getSelectionModel().getMaxSelectionIndex() < 0 - || selectedIndex > jTable1.getSelectionModel().getMaxSelectionIndex()) { + int selectedIndex = artifactsTable.getSelectionModel().getLeadSelectionIndex(); + if (selectedIndex < artifactsTable.getSelectionModel().getMinSelectionIndex() + || artifactsTable.getSelectionModel().getMaxSelectionIndex() < 0 + || selectedIndex > artifactsTable.getSelectionModel().getMaxSelectionIndex()) { return null; } - return tableModel.getArtifactByRow(jTable1.convertRowIndexToModel(selectedIndex)); + return tableModel.getArtifactByRow(artifactsTable.convertRowIndexToModel(selectedIndex)); } - /** - * Add the specified list of artifacts to the list of artifacts which should - * be displayed. - * - * @param artifacttList The list of artifacts to display. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - void addArtifacts(List artifacttList) { - tableModel.setContents(artifacttList); - jTable1.validate(); - jTable1.repaint(); + @Override + void addArtifacts(List artifactList) { + tableModel.setContents(artifactList); + artifactsTable.validate(); + artifactsTable.repaint(); tableModel.fireTableDataChanged(); } - /** - * Remove all artifacts from the list of artifacts displayed. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override void clearList() { tableModel.setContents(new ArrayList<>()); tableModel.fireTableDataChanged(); @@ -149,7 +135,7 @@ class MiniTimelineArtifactListPanel extends JPanel { private void initComponents() { //This class is a refactored copy of ArtifactsListPanel so lacks the form however the init method still constructs the proper UI elements. javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane(); - jTable1 = new javax.swing.JTable(); + artifactsTable = new javax.swing.JTable(); setOpaque(false); setPreferredSize(new java.awt.Dimension(300, 0)); @@ -157,10 +143,10 @@ class MiniTimelineArtifactListPanel extends JPanel { jScrollPane1.setBorder(null); jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 0)); - jTable1.setAutoCreateRowSorter(true); - jTable1.setModel(tableModel); - jTable1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - jScrollPane1.setViewportView(jTable1); + artifactsTable.setAutoCreateRowSorter(true); + artifactsTable.setModel(tableModel); + artifactsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(artifactsTable); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -191,7 +177,7 @@ class MiniTimelineArtifactListPanel extends JPanel { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void setContents(List artifactList) { - jTable1.clearSelection(); + artifactsTable.clearSelection(); this.artifactList.clear(); this.artifactList.addAll(artifactList); } @@ -266,6 +252,6 @@ class MiniTimelineArtifactListPanel extends JPanel { } // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTable jTable1; + private javax.swing.JTable artifactsTable; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java index af4c09d8f2..ad7dc0bcb5 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java @@ -51,6 +51,7 @@ class MiniTimelinePanel extends javax.swing.JPanel { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) MiniTimelinePanel() { initComponents(); + artifactListPanel.addMouseListener(new ArtifactMenuMouseAdapter(artifactListPanel)); artifactListener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent event) { @@ -73,7 +74,7 @@ class MiniTimelinePanel extends javax.swing.JPanel { @Override public void valueChanged(ListSelectionEvent event) { if (!event.getValueIsAdjusting()) { - artifactListPanel.removeListSelectionListener(artifactListener); + artifactListPanel.removeSelectionListener(artifactListener); artifactListPanel.clearList(); artifactListPanel.addArtifacts(dateListPanel.getArtifactsForSelectedDate()); artifactListPanel.addSelectionListener(artifactListener); @@ -127,7 +128,7 @@ class MiniTimelinePanel extends javax.swing.JPanel { void handleMiniTimelineResultEvent(DiscoveryEventUtils.MiniTimelineResultEvent miniTimelineResultEvent) { SwingUtilities.invokeLater(() -> { dateListPanel.removeListSelectionListener(dateListener); - artifactListPanel.removeListSelectionListener(artifactListener); + artifactListPanel.removeSelectionListener(artifactListener); dateListPanel.addArtifacts(miniTimelineResultEvent.getResultList()); status = DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATED; setEnabled(!dateListPanel.isEmpty());