From 058ef8f0c832a2936772bd7cfdd3bf5e91d2ddc4 Mon Sep 17 00:00:00 2001 From: Tim McIver Date: Mon, 18 Mar 2013 12:33:07 -0400 Subject: [PATCH 01/25] Added 'Timestamp' column to inbox table. --- .../autopsy/ingest/IngestMessagePanel.java | 43 +++++-------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index 094b8efdc2..a785509362 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java @@ -60,10 +60,6 @@ class IngestMessagePanel extends javax.swing.JPanel { private volatile int lastRowSelected = -1; private volatile long totalMessages = 0; - private enum COLUMN { - - SUBJECT, COUNT, MODULE - }; private static PropertyChangeSupport messagePcs = new PropertyChangeSupport(IngestMessagePanel.class); static final String MESSAGE_CHANGE_EVT = "MESSAGE_CHANGE_EVT"; //number of unread messages changed @@ -233,7 +229,8 @@ class IngestMessagePanel extends javax.swing.JPanel { messageTable.getParent().setBackground(messageTable.getBackground()); renderer = new MessageTableRenderer(); - for (int i = 0; i < 3; i++) { + int numCols = messageTable.getColumnCount(); + for (int i = 0; i < numCols; ++i) { TableColumn column = messageTable.getColumnModel().getColumn(i); //column.setCellRenderer(new MessageTableRenderer()); column.setCellRenderer(renderer); @@ -253,15 +250,10 @@ class IngestMessagePanel extends javax.swing.JPanel { } void setTableSize(Dimension d) { - for (int i = 0; i < 3; ++i) { - TableColumn column = messageTable.getColumnModel().getColumn(i); - if (i == 0) { - column.setPreferredWidth(((int) (d.width * 0.23))); - } else if (i == 1) { - column.setPreferredWidth(((int) (d.width * 0.09))); - } else { - column.setPreferredWidth(((int) (d.width * 0.67))); - } + double[] columnWidths = new double[]{0.20, 0.1, 0.45, 0.25}; + int numCols = messageTable.getColumnCount(); + for (int i = 0; i < numCols; ++i) { + messageTable.getColumnModel().getColumn(i).setPreferredWidth((int)(d.width * columnWidths[i])); } } @@ -319,6 +311,7 @@ class IngestMessagePanel extends javax.swing.JPanel { private class MessageTableModel extends AbstractTableModel { //data + private String[] columnNames = new String[]{"Module", "Num", "Subject", "Timestamp"}; private List messageData = new ArrayList(); //for keeping track of messages to group, per module, by uniqness private Map>> groupings = new HashMap>>(); @@ -343,7 +336,7 @@ class IngestMessagePanel extends javax.swing.JPanel { @Override public int getColumnCount() { - return 3; + return columnNames.length; } @Override @@ -385,23 +378,7 @@ class IngestMessagePanel extends javax.swing.JPanel { @Override public String getColumnName(int column) { - String colName = null; - - switch (column) { - case 0: - colName = "Module"; - break; - case 1: - colName = "Num"; - break; - case 2: - colName = "Subject"; - break; - default: - ; - - } - return colName; + return columnNames[column]; } @Override @@ -432,6 +409,8 @@ class IngestMessagePanel extends javax.swing.JPanel { case 2: ret = (Object) entry.messageGroup.getSubject(); break; + case 3: + ret = entry.messageGroup.getDatePosted(); default: logger.log(Level.SEVERE, "Invalid table column index: " + columnIndex); break; From d54f6093fa8ea4636d73999d4adff2e19392f19a Mon Sep 17 00:00:00 2001 From: Tim McIver Date: Tue, 19 Mar 2013 17:36:39 -0400 Subject: [PATCH 02/25] Updates to ingest message (inbox) infrastructure that adds a 'New?' column to the table with a check box. All messages in the inbox have their check marks removed when the inbox is closed. Messages that come in to the inbox there after will have a check in this column. --- .../ingest/IngestMessageMainPanel.java | 4 + .../autopsy/ingest/IngestMessagePanel.java | 105 ++++++++++-------- .../ingest/IngestMessageTopComponent.java | 12 +- 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java index 9566ab1082..51758b6239 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java @@ -43,6 +43,10 @@ public class IngestMessageMainPanel extends JLayeredPane { initComponents(); customizeComponents(); } + + public void markAllSeen() { + messagePanel.markAllSeen(); + } private void customizeComponents() { messagePanel = new IngestMessagePanel(this); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index a785509362..be2d0cfc0a 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java @@ -41,6 +41,7 @@ import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumn; +import org.apache.log4j.lf5.LogLevel; import org.sleuthkit.autopsy.ingest.IngestMessage.*; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -70,6 +71,10 @@ class IngestMessagePanel extends javax.swing.JPanel { initComponents(); customizeComponents(); } + + public void markAllSeen() { + tableModel.markAllSeen(); + } int getLastRowSelected() { return this.lastRowSelected; @@ -230,7 +235,9 @@ class IngestMessagePanel extends javax.swing.JPanel { renderer = new MessageTableRenderer(); int numCols = messageTable.getColumnCount(); - for (int i = 0; i < numCols; ++i) { + + // add the cell renderer to all columns except the last one (New?) + for (int i = 0; i < numCols - 1; ++i) { TableColumn column = messageTable.getColumnModel().getColumn(i); //column.setCellRenderer(new MessageTableRenderer()); column.setCellRenderer(renderer); @@ -250,7 +257,7 @@ class IngestMessagePanel extends javax.swing.JPanel { } void setTableSize(Dimension d) { - double[] columnWidths = new double[]{0.20, 0.1, 0.45, 0.25}; + double[] columnWidths = new double[]{0.20, 0.08, 0.39, 0.25, 0.08}; int numCols = messageTable.getColumnCount(); for (int i = 0; i < numCols; ++i) { messageTable.getColumnModel().getColumn(i).setPreferredWidth((int)(d.width * columnWidths[i])); @@ -309,9 +316,8 @@ class IngestMessagePanel extends javax.swing.JPanel { } private class MessageTableModel extends AbstractTableModel { - //data - private String[] columnNames = new String[]{"Module", "Num", "Subject", "Timestamp"}; + private String[] columnNames = new String[]{"Module", "Num", "Subject", "Timestamp", "New?"}; private List messageData = new ArrayList(); //for keeping track of messages to group, per module, by uniqness private Map>> groupings = new HashMap>>(); @@ -338,11 +344,17 @@ class IngestMessagePanel extends javax.swing.JPanel { public int getColumnCount() { return columnNames.length; } - + @Override synchronized public int getRowCount() { return getNumberGroups(); } + + public void markAllSeen() { + for (TableEntry entry : messageData) { + entry.hasBeenSeen(true); + } + } synchronized int getNumberGroups() { return messageData.size(); @@ -351,7 +363,7 @@ class IngestMessagePanel extends javax.swing.JPanel { synchronized int getNumberMessages() { int total = 0; for (TableEntry e : messageData) { - total += e.messageGroup.count; + total += e.messageGroup.getCount(); } return total; } @@ -359,8 +371,8 @@ class IngestMessagePanel extends javax.swing.JPanel { synchronized int getNumberUnreadMessages() { int total = 0; for (TableEntry e : messageData) { - if (e.visited == false) { - total += e.messageGroup.count; + if (!e.hasBeenVisited) { + total += e.messageGroup.getCount(); } } return total; @@ -369,7 +381,7 @@ class IngestMessagePanel extends javax.swing.JPanel { synchronized int getNumberUnreadGroups() { int total = 0; for (TableEntry e : messageData) { - if (e.visited == false) { + if (!e.hasBeenVisited) { ++total; } } @@ -386,7 +398,8 @@ class IngestMessagePanel extends javax.swing.JPanel { Object ret = null; int numMessages = messageData.size(); - if (rowIndex > messageData.size() -1) { + if (rowIndex > messageData.size() - 1 || + columnIndex > columnNames.length - 1) { //temporary check if the rare case still occurrs //#messages is now lower after last regrouping, and gui event thinks it's not logger.log(Level.WARNING, "Requested inbox message at" + rowIndex, ", only have " + numMessages); @@ -397,20 +410,20 @@ class IngestMessagePanel extends javax.swing.JPanel { switch (columnIndex) { case 0: Object module = entry.messageGroup.getSource(); - if (module == null) { - ret = ""; - } else { - ret = (Object) entry.messageGroup.getSource().getName(); - } + ret = module == null ? "" : entry.messageGroup.getSource().getName(); break; case 1: - ret = (Object) entry.messageGroup.getCount(); + ret = entry.messageGroup.getCount(); break; case 2: - ret = (Object) entry.messageGroup.getSubject(); + ret = entry.messageGroup.getSubject(); break; case 3: ret = entry.messageGroup.getDatePosted(); + break; + case 4: + ret = !entry.hasBeenSeen(); + break; default: logger.log(Level.SEVERE, "Invalid table column index: " + columnIndex); break; @@ -539,7 +552,7 @@ class IngestMessagePanel extends javax.swing.JPanel { } public synchronized void setVisited(int rowNumber) { - messageData.get(rowNumber).visited = true; + messageData.get(rowNumber).hasBeenVisited(true); //repaint the cell fireTableCellUpdated(rowNumber, 2); } @@ -547,8 +560,8 @@ class IngestMessagePanel extends javax.swing.JPanel { public synchronized void setVisitedAll() { int row = 0; for (TableEntry e : messageData) { - if (e.visited == false) { - e.visited = true; + if (!e.hasBeenVisited) { + e.hasBeenVisited(true); fireTableCellUpdated(row, 2); } ++row; @@ -556,7 +569,7 @@ class IngestMessagePanel extends javax.swing.JPanel { } public synchronized boolean isVisited(int rowNumber) { - return messageData.get(rowNumber).visited; + return messageData.get(rowNumber).hasBeenVisited(); } public synchronized MessageType getMessageType(int rowNumber) { @@ -580,11 +593,27 @@ class IngestMessagePanel extends javax.swing.JPanel { class TableEntry implements Comparable { IngestMessageGroup messageGroup; - boolean visited; + boolean hasBeenVisited = false; + boolean hasBeenSeen = false; + + public boolean hasBeenVisited() { + return hasBeenVisited; + } + + public void hasBeenVisited(boolean visited) { + hasBeenVisited = visited; + } + + public boolean hasBeenSeen() { + return hasBeenSeen; + } + + public void hasBeenSeen(boolean seen) { + hasBeenSeen = seen; + } TableEntry(IngestMessageGroup messageGroup) { this.messageGroup = messageGroup; - visited = false; } @Override @@ -592,7 +621,7 @@ class IngestMessagePanel extends javax.swing.JPanel { if (chronoSort == true) { return this.messageGroup.getDatePosted().compareTo(o.messageGroup.getDatePosted()); } else { - return messageGroup.count - o.messageGroup.count; + return messageGroup.getCount() - o.messageGroup.getCount(); } } } @@ -607,12 +636,10 @@ class IngestMessagePanel extends javax.swing.JPanel { static final Color MED_PRI_COLOR = new Color(199, 199, 222); static final Color LOW_PRI_COLOR = new Color(221, 221, 235); private List messages; - private int count; IngestMessageGroup(IngestMessage message) { messages = new ArrayList(); messages.add(message); - count = 1; } List getMessages() { @@ -620,39 +647,18 @@ class IngestMessagePanel extends javax.swing.JPanel { } void add(IngestMessage message) { - - IngestMessage first = messages.get(0); - //make sure uniqness agrees - /* - if (!message.getSource().equals(first.getSource()) - || !message.getUniqueKey().equals(first.getUniqueKey())) { - throw new IllegalArgumentException("Tried to add a message to a wrong message group."); - } */ - messages.add(message); - ++count; } //add all messages from another group void addAll(IngestMessageGroup group) { - - //IngestMessage first = messages.get(0); - //IngestMessage firstG = group.messages.get(0); - //make sure uniqness agrees - /* - if (!firstG.getSource().equals(first.getSource()) - || !firstG.getUniqueKey().equals(first.getUniqueKey())) { - throw new IllegalArgumentException("Tried to add a message to a wrong message group."); - } */ - for (IngestMessage m : group.getMessages()) { messages.add(m); - ++count; } } int getCount() { - return count; + return messages.size(); } String getDetails() { @@ -675,6 +681,7 @@ class IngestMessagePanel extends javax.swing.JPanel { * @return */ Color getColor() { + int count = messages.size(); if (count == 1) { return VERY_HIGH_PRI_COLOR; } else if (count < 5) { @@ -693,7 +700,7 @@ class IngestMessagePanel extends javax.swing.JPanel { * @return */ Date getDatePosted() { - return messages.get(count - 1).getDatePosted(); + return messages.get(messages.size() - 1).getDatePosted(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java index 0cbdad6d77..30245aae8d 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java @@ -136,15 +136,9 @@ public final class IngestMessageTopComponent extends TopComponent implements Ing //logger.log(Level.INFO, "CLOSED"); super.componentClosed(); - /* - Mode mode = WindowManager.getDefault().findMode("dockedBottom"); - if (mode != null) { - mode.dockInto(this); - this.open(); - } - * */ - - //this.close(); + // mark all the messages as seen (this will make the 'New?' columen NOT + // show a ckeckmark) + messagePanel.markAllSeen(); } @Override From 49cd4b54eba417e1d36881eb193c61f2184eb6cb Mon Sep 17 00:00:00 2001 From: Tim McIver Date: Thu, 21 Mar 2013 15:56:09 -0400 Subject: [PATCH 03/25] Refactored TableCellRenerer to have several classes with a single 'point-of -entry' (MessageTableRenderer) to dispatch to other TableCellRenderers based on things such as column data type. Changed date display to display only on the time without the date. --- .../autopsy/ingest/IngestMessagePanel.form | 2 +- .../autopsy/ingest/IngestMessagePanel.java | 213 +++++++++++++----- 2 files changed, 155 insertions(+), 60 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.form b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.form index 280a1c571d..218a850c0f 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.form @@ -55,7 +55,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index be2d0cfc0a..a31b5bac08 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java @@ -26,6 +26,8 @@ import java.awt.Font; import java.awt.Graphics; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -33,15 +35,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; +import javax.swing.JLabel; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JTable; import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableColumn; -import org.apache.log4j.lf5.LogLevel; +import javax.swing.table.TableCellRenderer; import org.sleuthkit.autopsy.ingest.IngestMessage.*; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -54,8 +57,8 @@ class IngestMessagePanel extends javax.swing.JPanel { private MessageTableModel tableModel; private MessageTableRenderer renderer; private IngestMessageMainPanel mainPanel; - private static Font visitedFont = new Font("Arial", Font.PLAIN, 11); - private static Font notVisitedFont = new Font("Arial", Font.BOLD, 11); + private static Font visitedFont = new Font("Arial", Font.PLAIN, 12); + private static Font notVisitedFont = new Font("Arial", Font.BOLD, 12); private static Color ERROR_COLOR = new Color(255, 90, 90); private boolean resized = false; private volatile int lastRowSelected = -1; @@ -121,7 +124,7 @@ class IngestMessagePanel extends javax.swing.JPanel { jScrollPane1.setOpaque(false); messageTable.setBackground(new java.awt.Color(221, 221, 235)); - messageTable.setFont(new java.awt.Font("Arial", 0, 10)); // NOI18N + messageTable.setFont(new java.awt.Font("Arial", 0, 12)); // NOI18N messageTable.setModel(tableModel); messageTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF); messageTable.setAutoscrolls(false); @@ -236,11 +239,9 @@ class IngestMessagePanel extends javax.swing.JPanel { renderer = new MessageTableRenderer(); int numCols = messageTable.getColumnCount(); - // add the cell renderer to all columns except the last one (New?) - for (int i = 0; i < numCols - 1; ++i) { - TableColumn column = messageTable.getColumnModel().getColumn(i); - //column.setCellRenderer(new MessageTableRenderer()); - column.setCellRenderer(renderer); + // add the cell renderer to all columns + for (int i = 0; i < numCols; ++i) { + messageTable.getColumnModel().getColumn(i).setCellRenderer(renderer); } messageTable.setCellSelectionEnabled(false); @@ -257,7 +258,7 @@ class IngestMessagePanel extends javax.swing.JPanel { } void setTableSize(Dimension d) { - double[] columnWidths = new double[]{0.20, 0.08, 0.39, 0.25, 0.08}; + double[] columnWidths = new double[]{0.20, 0.08, 0.08, 0.49, 0.15}; int numCols = messageTable.getColumnCount(); for (int i = 0; i < numCols; ++i) { messageTable.getColumnModel().getColumn(i).setPreferredWidth((int)(d.width * columnWidths[i])); @@ -310,14 +311,14 @@ class IngestMessagePanel extends javax.swing.JPanel { private synchronized void setVisited(int rowNumber) { final int origMsgGroups = tableModel.getNumberUnreadGroups(); tableModel.setVisited(rowNumber); - renderer.setSelected(rowNumber); + //renderer.setSelected(rowNumber); lastRowSelected = rowNumber; messagePcs.firePropertyChange(TOOL_TIP_TEXT_KEY, origMsgGroups, tableModel.getNumberUnreadGroups()); } private class MessageTableModel extends AbstractTableModel { - private String[] columnNames = new String[]{"Module", "Num", "Subject", "Timestamp", "New?"}; + private String[] columnNames = new String[]{"Module", "Num", "New?", "Subject", "Timestamp"}; private List messageData = new ArrayList(); //for keeping track of messages to group, per module, by uniqness private Map>> groupings = new HashMap>>(); @@ -416,13 +417,13 @@ class IngestMessagePanel extends javax.swing.JPanel { ret = entry.messageGroup.getCount(); break; case 2: - ret = entry.messageGroup.getSubject(); + ret = !entry.hasBeenSeen(); break; case 3: - ret = entry.messageGroup.getDatePosted(); + ret = entry.messageGroup.getSubject(); break; case 4: - ret = !entry.hasBeenSeen(); + ret = entry.messageGroup.getDatePosted(); break; default: logger.log(Level.SEVERE, "Invalid table column index: " + columnIndex); @@ -739,43 +740,66 @@ class IngestMessagePanel extends javax.swing.JPanel { return messages.get(0).getMessageType(); } } - - /** - * bold font if not visited, colors for errors - * tooltips that show entire query string, disable selection borders + + /* + * Main TableCellRenderer to be used for ingest message inbox. Delegates to + * other TableCellRenderers based different factors such as column data type + * or column number. */ private class MessageTableRenderer extends DefaultTableCellRenderer { + + private TableCellRenderer booleanRenderer = new BooleanRenderer(); + private TableCellRenderer defaultRenderer = new DefaultRenderer(); + private TableCellRenderer dateRenderer = new DateRenderer(); + protected int rowSelected; - //custom selection tracking - private int selected = -1; - - void setSelected(int row) { - this.selected = row; + public void setRowSelected(int rowSelected) { + this.rowSelected = rowSelected; } @Override - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) { - - final Component cell = super.getTableCellRendererComponent( - table, value, false, false, row, column); - - if (column == 2) { - String val = (String) table.getModel().getValueAt(row, column); - setToolTipText(val); - //setText(val); - if (tableModel.isVisited(row)) { - cell.setFont(visitedFont); - } else { - cell.setFont(notVisitedFont); - } + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value instanceof Boolean) { + return booleanRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } else if (value instanceof Date) { + return dateRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } else { + return defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } + } + } + + /* + * TableCellRenderer used to render boolean values with a bullet point. + */ + private class BooleanRenderer extends DefaultTableCellRenderer { + + private final String bulletChar = new String(Character.toChars(0x2022)); - - //if (!isSelected) { - if (selected != row) { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + + boolean boolVal; + if (value instanceof Boolean) { + boolVal = ((Boolean)value).booleanValue(); + } else { + throw new RuntimeException("Tried to use BooleanRenderer on non-boolean value."); + } + + String aValue = boolVal ? bulletChar : ""; + + JLabel cell = (JLabel)super.getTableCellRendererComponent(table, aValue, isSelected, hasFocus, row, column); + + // center the bullet in the JLabel + cell.setHorizontalAlignment(SwingConstants.CENTER); + + // increase the font size + cell.setFont(new Font("", Font.PLAIN, 16)); + + if (isSelected) { final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); MessageType mt = messageGroup.getMessageType(); if (mt == MessageType.ERROR) { @@ -790,25 +814,96 @@ class IngestMessagePanel extends javax.swing.JPanel { super.setForeground(table.getSelectionForeground()); super.setBackground(table.getSelectionBackground()); } - //} - /* - if (column == 1) { - if (isSelected) { - super.setForeground(table.getSelectionForeground()); - super.setBackground(table.getSelectionBackground()); - } else { - cell.setBackground(table.getBackground()); - } - } */ - - return this; + + return cell; } + + } + + /** + * bold font if not visited, colors for errors + * tooltips that show entire query string, disable selection borders + */ + private class DefaultRenderer extends DefaultTableCellRenderer { @Override - protected void setValue(Object value) { - super.setValue(value); + public Component getTableCellRendererComponent( + JTable table, Object value, + boolean isSelected, boolean hasFocus, + int row, int column) { + + Component cell = super.getTableCellRendererComponent( + table, value, false, false, row, column); + + if (column == 3) { + String subject = (String)value; + setToolTipText(subject); + if (tableModel.isVisited(row)) { + cell.setFont(visitedFont); + } else { + cell.setFont(notVisitedFont); + } + } + + if (isSelected) { + final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); + MessageType mt = messageGroup.getMessageType(); + if (mt == MessageType.ERROR) { + cell.setBackground(ERROR_COLOR); + } else if (mt == MessageType.WARNING) { + cell.setBackground(Color.orange); + } else { + //cell.setBackground(table.getBackground()); + cell.setBackground(messageGroup.getColor()); + } + } else { + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } + + return cell; } } + + private class DateRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + + Object aValue = value; + if (value instanceof Date) { + Date date = (Date)value; + DateFormat df = new SimpleDateFormat("HH:mm:ss"); + aValue = df.format(date); + } else { + throw new RuntimeException("Tried to use DateRenderer on non-Date value."); + } + + Component cell = super.getTableCellRendererComponent(table, aValue, isSelected, hasFocus, row, column); + + if (isSelected) { + final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); + MessageType mt = messageGroup.getMessageType(); + if (mt == MessageType.ERROR) { + cell.setBackground(ERROR_COLOR); + } else if (mt == MessageType.WARNING) { + cell.setBackground(Color.orange); + } else { + //cell.setBackground(table.getBackground()); + cell.setBackground(messageGroup.getColor()); + } + } else { + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } + + return cell; + } + + } /** * handle table selections / cell visitations From 72036fb8d7dc71ce509786bc2f8c4cfa6547e5c3 Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 21 Mar 2013 23:20:19 -0400 Subject: [PATCH 04/25] Redesign and refactored Media Viewer into video and image viewers, using UI builder and layers. Smoother flipping /eliminated flicker when switching between images. Add info message area and message if can't play deleted video. --- .../DataContentViewerMedia.form | 82 +- .../DataContentViewerMedia.java | 752 ++---------------- .../corecomponents/MediaViewImagePanel.form | 19 + .../corecomponents/MediaViewImagePanel.java | 178 +++++ .../corecomponents/MediaViewVideoPanel.form | 114 +++ .../corecomponents/MediaViewVideoPanel.java | 635 +++++++++++++++ 6 files changed, 1027 insertions(+), 753 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form index 2dc5d049de..20985cd415 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form @@ -1,6 +1,11 @@
+ + + + + @@ -13,80 +18,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index e49dd6da87..8aadc0ec65 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,95 +18,58 @@ */ package org.sleuthkit.autopsy.corecomponents; -import com.sun.javafx.application.PlatformImpl; +import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.IntBuffer; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import javafx.application.Platform; -import javafx.embed.swing.JFXPanel; -import javafx.embed.swing.SwingFXUtils; -import javafx.scene.Group; -import javafx.scene.Scene; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javax.imageio.ImageIO; import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.BoxLayout; import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import org.gstreamer.*; -import org.gstreamer.elements.PlayBin2; -import org.gstreamer.elements.RGBDataSink; -import org.gstreamer.swing.VideoComponent; -import org.netbeans.api.progress.ProgressHandle; -import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.nodes.Node; -import org.openide.util.Cancellable; -import org.openide.util.Exceptions; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; -import org.sleuthkit.autopsy.corelibs.ScalrWrapper; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; /** - * Media content viewer for videos, sounds and images. Using gstreamer. + * Media content viewer for videos, sounds and images. */ @ServiceProviders(value = { - @ServiceProvider(service = DataContentViewer.class, position = 5), - @ServiceProvider(service = FrameCapture.class) + @ServiceProvider(service = DataContentViewer.class, position = 5) }) -public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer, FrameCapture { +public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { private String[] IMAGES; // use javafx supported private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg"}; private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; - private static final int NUM_FRAMES = 12; - private static final long MIN_FRAME_INTERVAL_MILLIS = 500; + private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); - private VideoComponent videoComponent; - private PlayBin2 playbin2; - private AbstractFile currentFile; - private long durationMillis = 0; - private boolean autoTracking = false; // true if the slider is moving automatically - private final Object playbinLock = new Object(); // lock for synchronization of playbin2 player - private VideoProgressWorker videoProgressWorker; - private int totalHours, totalMinutes, totalSeconds; - private BufferedImage currentImage = null; - private boolean gstInited = false; + private AbstractFile lastFile; - private boolean inImageMode; //keeps track if already in image mode to minimize UI setup + //UI + private final MediaViewVideoPanel videoPanel; + private final MediaViewImagePanel imagePanel; + + private static final String IMAGE_VIEWER_LAYER = "IMAGE"; + private static final String VIDEO_VIEWER_LAYER = "VIDEO"; /** * Creates new form DataContentViewerVideo */ public DataContentViewerMedia() { + initComponents(); + + videoPanel = new MediaViewVideoPanel(); + imagePanel = new MediaViewImagePanel(); + customizeComponents(); logger.log(Level.INFO, "Created MediaView instance: " + this); } private void customizeComponents() { - inImageMode = false; - logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); //initialize supported image types //TODO use mime-types instead once we have support @@ -118,47 +81,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo IMAGES[i] = "." + suffix; } - try { - logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); - Gst.init(); - gstInited = true; - } catch (GstException e) { - gstInited = false; - logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e); - MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. " - + " Video and audio viewing will be disabled. ", - e.getMessage()); - return; - } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { - gstInited = false; - logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e); - MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. " - + " Video and audio viewing will be disabled. ", - e.getMessage()); - return; - } - - progressSlider.setEnabled(false); // disable slider; enable after user plays vid - progressSlider.addChangeListener(new ChangeListener() { - /** - * Should always try to synchronize any call to - * progressSlider.setValue() to avoid a different thread changing - * playbin while stateChanged() is processing - */ - @Override - public void stateChanged(ChangeEvent e) { - int time = progressSlider.getValue(); - synchronized (playbinLock) { - if (playbin2 != null && !autoTracking) { - State orig = playbin2.getState(); - playbin2.pause(); - playbin2.seek(ClockTime.fromMillis(time)); - playbin2.setState(orig); - } - } - } - }); + add(imagePanel, IMAGE_VIEWER_LAYER); + add(videoPanel, VIDEO_VIEWER_LAYER); + + switchPanels(false); + + } /** @@ -170,89 +99,17 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo // //GEN-BEGIN:initComponents private void initComponents() { - pauseButton = new javax.swing.JButton(); - videoPanel = new javax.swing.JPanel(); - progressSlider = new javax.swing.JSlider(); - progressLabel = new javax.swing.JLabel(); - - pauseButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.pauseButton.text")); // NOI18N - pauseButton.setMaximumSize(new java.awt.Dimension(45, 23)); - pauseButton.setMinimumSize(new java.awt.Dimension(45, 23)); - pauseButton.setPreferredSize(new java.awt.Dimension(45, 23)); - pauseButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - pauseButtonActionPerformed(evt); - } - }); - - javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); - videoPanel.setLayout(videoPanelLayout); - videoPanelLayout.setHorizontalGroup( - videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) - ); - videoPanelLayout.setVerticalGroup( - videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 242, Short.MAX_VALUE) - ); - - progressSlider.setValue(0); - - progressLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); - progressLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.progressLabel.text")); // NOI18N - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addComponent(pauseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 104, javax.swing.GroupLayout.PREFERRED_SIZE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(pauseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - ); + setLayout(new java.awt.CardLayout()); + getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.AccessibleContext.accessibleDescription")); // NOI18N }// //GEN-END:initComponents - - private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed - synchronized (playbinLock) { - State state = playbin2.getState(); - if (state.equals(State.PLAYING)) { - playbin2.pause(); - pauseButton.setText("►"); - playbin2.setState(State.PAUSED); - } else if (state.equals(State.PAUSED)) { - playbin2.play(); - pauseButton.setText("||"); - playbin2.setState(State.PLAYING); - } else if (state.equals(State.READY)) { - ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); - em.execute(); - em.getExtractedBytes(); - } - } - }//GEN-LAST:event_pauseButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton pauseButton; - private javax.swing.JLabel progressLabel; - private javax.swing.JSlider progressSlider; - private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables @Override public void setNode(Node selectedNode) { + videoPanel.reset(); + if (selectedNode == null) { return; } @@ -268,214 +125,36 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo lastFile = file; } - reset(); - setComponentsVisibility(false); - - // get rid of any existing videoProgressWorker thread - if (videoProgressWorker != null) { - videoProgressWorker.cancel(true); - videoProgressWorker = null; - } - - - currentFile = file; + + + final Dimension dims = DataContentViewerMedia.this.getSize(); + if (containsExt(file.getName(), IMAGES)) { - showImageFx(file); - } else if (gstInited + imagePanel.showImageFx(file, dims); + this.switchPanels(false); + } else if (videoPanel.isInited() && (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) { - inImageMode = false; - setupVideo(file); + + videoPanel.setupVideo(file, dims); + this.switchPanels(true); } } /** - * Initialize vars and display the image on the panel. + * switch to visible video or image panel * - * @param file + * @param showVideo true if video panel, false if image panel */ - private void showImageFx(final AbstractFile file) { - final String fileName = file.getName(); - - // load the image - PlatformImpl.runLater(new Runnable() { - @Override - public void run() { - if (!Case.isCaseOpen()) { - //handle in-between condition when case is being closed - //and an image was previously selected - return; - } - - Dimension dims = DataContentViewerMedia.this.getSize(); - - final InputStream inputStream = new ReadContentInputStream(file); - - final Image fxImage; - try { - //original input stream - BufferedImage bi = ImageIO.read(inputStream); - //scale image using Scalr - BufferedImage biScaled = ScalrWrapper.resizeHighQuality(bi, (int) dims.getWidth(), (int) dims.getHeight()); - //convert from awt imageto fx image - fxImage = SwingFXUtils.toFXImage(biScaled, null); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not load image file into media view: " + fileName, ex); - return; - } catch (OutOfMemoryError ex) { - logger.log(Level.WARNING, "Could not load image file into media view (too large): " + fileName, ex); - MessageNotifyUtil.Notify.warn("Could not load image file (too large): " + file.getName(), ex.getMessage()); - return; - } finally { - try { - inputStream.close(); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not close input stream after loading image in media view: " + fileName, ex); - } - } - - if (fxImage == null) { - logger.log(Level.WARNING, "Could not load image file into media view: " + fileName); - return; - } - - // simple displays ImageView the image as is - ImageView fxImageView = new ImageView(); - fxImageView.setImage(fxImage); - // resizes the image to have width of 100 while preserving the ratio and using - // higher quality filtering method; this ImageView is also cached to - // improve performance - fxImageView.setPreserveRatio(true); - fxImageView.setSmooth(true); - fxImageView.setCache(true); - fxImageView.setFitWidth(dims.getWidth()); - fxImageView.setFitHeight(dims.getHeight()); - - Group fxRoot = new Group(); - - //Scene fxScene = new Scene(fxRoot, dims.getWidth(), dims.getHeight(), javafx.scene.paint.Color.BLACK); - Scene fxScene = new Scene(fxRoot, javafx.scene.paint.Color.BLACK); - fxRoot.getChildren().add(fxImageView); - - if (inImageMode) { - final JFXPanel fxPanel = (JFXPanel) videoPanel.getComponent(0); - fxPanel.setScene(fxScene); - videoPanel.setVisible(true); - } else { - inImageMode = true; - final JFXPanel fxPanel = new JFXPanel(); - fxPanel.setScene(fxScene); - - //when done, join with the swing panel - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - //remove video panels and recreate image view panel - //TODO use swing layered pane to switch between different modes - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(fxPanel); - videoPanel.setVisible(true); - - if (fxImage.isError()) { - logger.log(Level.WARNING, "Could not load image file into media view: " + fileName); - //MessageNotifyUtil.Message.warn("Could not load image file: " + file.getName()); - } - } - }); - } - } - }); - - - + private void switchPanels(boolean showVideo) { + CardLayout layout = (CardLayout)this.getLayout(); + if (showVideo) { + layout.show(this, VIDEO_VIEWER_LAYER); + } else { + layout.show(this, IMAGE_VIEWER_LAYER); + } } - /** - * Initialize vars and display the image on the panel. - * - * @param file - * @deprecated using javafx for image display - */ - @Deprecated - private void showImageGst(AbstractFile file) { - if (!gstInited) { - return; - } - java.io.File ioFile = getJFile(file); - if (!ioFile.exists()) { - try { - ContentUtils.writeToFile(file, ioFile); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error buffering file", ex); - } - } - videoComponent = new VideoComponent(); - synchronized (playbinLock) { - if (playbin2 != null) { - playbin2.dispose(); - } - playbin2 = new PlayBin2("ImageViewer"); - playbin2.setVideoSink(videoComponent.getElement()); - } - - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(videoComponent); - videoPanel.revalidate(); - videoPanel.repaint(); - - synchronized (playbinLock) { - playbin2.setInputFile(ioFile); - playbin2.play(); - } - videoPanel.setVisible(true); - } - - /** - * Initialize all the necessary vars to play a video/audio file. - * - * @param file the file to play - */ - private void setupVideo(AbstractFile file) { - java.io.File ioFile = getJFile(file); - - pauseButton.setText("►"); - progressSlider.setValue(0); - - videoComponent = new VideoComponent(); - synchronized (playbinLock) { - if (playbin2 != null) { - playbin2.dispose(); - } - playbin2 = new PlayBin2("VideoPlayer"); - playbin2.setVideoSink(videoComponent.getElement()); - } - - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(videoComponent); - videoPanel.revalidate(); - videoPanel.repaint(); - - synchronized (playbinLock) { - playbin2.setInputFile(ioFile); - playbin2.setState(State.READY); - } - setComponentsVisibility(true); - } - - /** - * To set the visibility of specific components in this class. - * - * @param isVisible whether to show or hide the specific components - */ - private void setComponentsVisibility(boolean isVisible) { - pauseButton.setVisible(isVisible); - progressLabel.setVisible(isVisible); - progressSlider.setVisible(isVisible); - videoPanel.setVisible(isVisible); - } @Override public String getTitle() { @@ -484,7 +163,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public String getToolTip() { - return "Displays supported multimedia files"; + return "Displays supported multimedia files (images, videos, audio)"; } @Override @@ -503,37 +182,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo // because we already reset on each selected node } - private void reset() { - // reset the progress label text on the event dispatch thread - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressLabel.setText(""); - } - }); - - - if (!gstInited) { - return; - } - - synchronized (playbinLock) { - if (playbin2 != null) { - if (playbin2.isPlaying()) { - playbin2.stop(); - } - playbin2.setState(State.NULL); - if (playbin2.getState().equals(State.NULL)) { - playbin2.dispose(); - } - playbin2 = null; - } - videoComponent = null; - } - } + @Override public boolean isSupported(Node node) { + if (node == null) { return false; } @@ -543,10 +196,6 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo return false; } - //try displaying deleted files if we can read them - //if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - // return false; - //} if (file.getSize() == 0) { return false; @@ -554,15 +203,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo String name = file.getName().toLowerCase(); - boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC); - if (containsExt(name, IMAGES)) { return true; } //for gstreamer formats, check if initialized first, then - //support audio formats, and video formats if undeleted file - else if (gstInited + //support audio formats, and video formats + else if (videoPanel.isInited() && (containsExt(name, AUDIOS) - || (!deleted && containsExt(name, VIDEOS)))) { + || (containsExt(name, VIDEOS)))) { return true; } @@ -572,7 +219,19 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public int isPreferred(Node node, boolean isSupported) { if (isSupported) { - return 7; + //special case, check if deleted video, then do not make it preferred + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file == null) { + return 0; + } + String name = file.getName().toLowerCase(); + + boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC); + if (containsExt(name, VIDEOS) && deleted) { + return 0; + } else { + return 7; + } } else { return 0; } @@ -586,274 +245,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } return Arrays.asList(exts).contains(ext); } - - private java.io.File getJFile(AbstractFile file) { - // Get the temp folder path of the case - String tempPath = Case.getCurrentCase().getTempDirectory(); - String name = file.getName(); - int extStart = name.lastIndexOf("."); - String ext = ""; - if (extStart != -1) { - ext = name.substring(extStart, name.length()).toLowerCase(); - } - tempPath = tempPath + java.io.File.separator + file.getId() + ext; - - java.io.File tempFile = new java.io.File(tempPath); - return tempFile; - } - - @Override - public List captureFrames(java.io.File file, int numFrames) { - - List frames = new ArrayList<>(); - - if (!gstInited) { - return frames; - } - - RGBDataSink.Listener listener1 = new RGBDataSink.Listener() { - @Override - public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) { - BufferedImage curImage = new BufferedImage(w, h, - BufferedImage.TYPE_INT_ARGB); - curImage.setRGB(0, 0, w, h, rgbPixels.array(), 0, w); - currentImage = curImage; - } - }; - - // set up a PlayBin2 object - RGBDataSink videoSink = new RGBDataSink("rgb", listener1); - PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); - playbin.setInputFile(file); - playbin.setVideoSink(videoSink); - - // this is necessary to get a valid duration value - playbin.play(); - playbin.pause(); - playbin.getState(); - - // get the duration of the video - TimeUnit unit = TimeUnit.MILLISECONDS; - long myDurationMillis = playbin.queryDuration(unit); - if (myDurationMillis <= 0) { - return frames; - } - - // create a list of timestamps at which to get frames - int numFramesToGet = numFrames; - long frameInterval = myDurationMillis / numFrames; - if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { - numFramesToGet = 1; - } - - // for each timeStamp, grap a frame - for (int i = 0; i < numFramesToGet; ++i) { - long timeStamp = i * frameInterval; - - playbin.pause(); - playbin.getState(); - - currentImage = null; - if (!playbin.seek(timeStamp, unit)) { - logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); - } - playbin.play(); - - while (currentImage == null) { - System.out.flush(); // not sure why this is needed - } - - playbin.stop(); - - frames.add(new VideoFrame(currentImage, timeStamp)); - } - - return frames; - } - - /* Thread that extracts and plays a file */ - private class ExtractMedia extends SwingWorker { - - private ProgressHandle progress; - boolean success = false; - private AbstractFile sFile; - private java.io.File jFile; - private String duration; - private String position; - private long extractedBytes; - - ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { - this.sFile = sFile; - this.jFile = jFile; - } - - public long getExtractedBytes() { - return extractedBytes; - } - - @Override - protected Object doInBackground() throws Exception { - success = false; - progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() { - @Override - public boolean cancel() { - return ExtractMedia.this.cancel(true); - } - }); - progressLabel.setText("Buffering... "); - progress.start(); - progress.switchToDeterminate(100); - try { - extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error buffering file", ex); - } - success = true; - return null; - } - - /* clean up or start the worker threads */ - @Override - protected void done() { - try { - super.get(); //block and get all exceptions thrown while doInBackground() - } catch (CancellationException ex) { - logger.log(Level.INFO, "Media buffering was canceled."); - } catch (InterruptedException ex) { - logger.log(Level.INFO, "Media buffering was interrupted."); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); - } finally { - progress.finish(); - if (!this.isCancelled()) { - play(); - } - } - } - - private void play() { - if (jFile == null || !jFile.exists()) { - progressLabel.setText("Error buffering file"); - return; - } - ClockTime dur = null; - synchronized (playbinLock) { - playbin2.play(); // must play, then pause and get state to get duration. - playbin2.pause(); - playbin2.getState(); - dur = playbin2.queryDuration(); - } - duration = dur.toString(); - durationMillis = dur.toMillis(); - - // pick out the total hours, minutes, seconds - long durationSeconds = (int) durationMillis / 1000; - totalHours = (int) durationSeconds / 3600; - durationSeconds -= totalHours * 3600; - totalMinutes = (int) durationSeconds / 60; - durationSeconds -= totalMinutes * 60; - totalSeconds = (int) durationSeconds; - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressSlider.setMaximum((int) durationMillis); - progressSlider.setMinimum(0); - - synchronized (playbinLock) { - playbin2.play(); - } - pauseButton.setText("||"); - videoProgressWorker = new VideoProgressWorker(); - videoProgressWorker.execute(); - } - }); - } - } - - private class VideoProgressWorker extends SwingWorker { - - private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; - private long millisElapsed = 0; - private final long INTER_FRAME_PERIOD_MS = 20; - private final long END_TIME_MARGIN_MS = 50; - - private boolean isPlayBinReady() { - synchronized (playbinLock) { - return playbin2 != null && !playbin2.getState().equals(State.NULL); - } - } - - public void resetVideo() { - synchronized (playbinLock) { - if (playbin2 != null) { - playbin2.stop(); - playbin2.setState(State.READY); // ready to be played again - } - } - pauseButton.setText("►"); - progressSlider.setValue(0); - - String durationStr = String.format(durationFormat, 0, 0, 0, - totalHours, totalMinutes, totalSeconds); - progressLabel.setText(durationStr); - } - - /** - * @return true while millisElapsed is greater than END_TIME_MARGIN_MS - * from durationMillis. This is used to indicate when the video has - * ended because for some videos the time elapsed never becomes equal to - * the reported duration of the video. - */ - private boolean hasNotEnded() { - return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; - } - - @Override - protected Object doInBackground() throws Exception { - - // enable the slider - progressSlider.setEnabled(true); - - int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; - ClockTime pos = null; - while (hasNotEnded() && isPlayBinReady() && !isCancelled()) { - - synchronized (playbinLock) { - pos = playbin2.queryPosition(); - } - millisElapsed = pos.toMillis(); - - // pick out the elapsed hours, minutes, seconds - long secondsElapsed = millisElapsed / 1000; - elapsedHours = (int) secondsElapsed / 3600; - secondsElapsed -= elapsedHours * 3600; - elapsedMinutes = (int) secondsElapsed / 60; - secondsElapsed -= elapsedMinutes * 60; - elapsedSeconds = (int) secondsElapsed; - - String durationStr = String.format(durationFormat, - elapsedHours, elapsedMinutes, elapsedSeconds, - totalHours, totalMinutes, totalSeconds); - - progressLabel.setText(durationStr); - autoTracking = true; - progressSlider.setValue((int) millisElapsed); - autoTracking = false; - - try { - Thread.sleep(INTER_FRAME_PERIOD_MS); - } catch (InterruptedException ex) { - break; - } - } - - // disable the slider - progressSlider.setEnabled(false); - - resetVideo(); - - return null; - } - } } + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form new file mode 100644 index 0000000000..a13f4244a5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form @@ -0,0 +1,19 @@ + + +
+ + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java new file mode 100644 index 0000000000..4ce2d1b744 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -0,0 +1,178 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.corecomponents; + +import com.sun.javafx.application.PlatformImpl; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import javafx.embed.swing.JFXPanel; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javax.imageio.ImageIO; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corelibs.ScalrWrapper; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ReadContentInputStream; + +/** + * Container for the image viewer part of media view, on a layered pane. To be + * used with JavaFx image viewer only. + */ +public class MediaViewImagePanel extends javax.swing.JPanel { + + private JFXPanel fxPanel; + private ImageView fxImageView; + private static final Logger logger = Logger.getLogger(MediaViewImagePanel.class.getName()); + + /** + * Creates new form MediaViewImagePanel + */ + public MediaViewImagePanel() { + initComponents(); + setupFx(); + } + + /** + * Setup FX components + */ + private void setupFx() { + // load the image + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + fxPanel = new JFXPanel(); + fxImageView = new ImageView(); + // resizes the image to have width of 100 while preserving the ratio and using + // higher quality filtering method; this ImageView is also cached to + // improve performance + fxImageView.setPreserveRatio(true); + fxImageView.setSmooth(true); + fxImageView.setCache(true); + + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + add(fxPanel); + + //TODO + // setVisible(true); + } + }); + + + + } + }); + + + } + + /** + * Show image + * + * @param file image file to show + * @param dims dimension of the parent window + */ + void showImageFx(final AbstractFile file, final Dimension dims) { + final String fileName = file.getName(); + + // load the image + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + if (!Case.isCaseOpen()) { + //handle in-between condition when case is being closed + //and an image was previously selected + return; + } + + final InputStream inputStream = new ReadContentInputStream(file); + + final Image fxImage; + try { + //original input stream + BufferedImage bi = ImageIO.read(inputStream); + //scale image using Scalr + BufferedImage biScaled = ScalrWrapper.resizeHighQuality(bi, (int) dims.getWidth(), (int) dims.getHeight()); + //convert from awt imageto fx image + fxImage = SwingFXUtils.toFXImage(biScaled, null); + } catch (IOException ex) { + logger.log(Level.WARNING, "Could not load image file into media view: " + fileName, ex); + return; + } catch (OutOfMemoryError ex) { + logger.log(Level.WARNING, "Could not load image file into media view (too large): " + fileName, ex); + MessageNotifyUtil.Notify.warn("Could not load image file (too large): " + file.getName(), ex.getMessage()); + return; + } finally { + try { + inputStream.close(); + } catch (IOException ex) { + logger.log(Level.WARNING, "Could not close input stream after loading image in media view: " + fileName, ex); + } + } + + if (fxImage == null || fxImage.isError()) { + logger.log(Level.WARNING, "Could not load image file into media view: " + fileName); + return; + } + + // simple displays ImageView the image as is + + fxImageView.setImage(fxImage); + fxImageView.setFitWidth(dims.getWidth()); + fxImageView.setFitHeight(dims.getHeight()); + + Group fxRoot = new Group(); + + //Scene fxScene = new Scene(fxRoot, dims.getWidth(), dims.getHeight(), javafx.scene.paint.Color.BLACK); + Scene fxScene = new Scene(fxRoot, javafx.scene.paint.Color.BLACK); + fxRoot.getChildren().add(fxImageView); + fxPanel.setScene(fxScene); + //TODO + //setVisible(true); + } + }); + + + + } + + /** + * 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() { + + setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form new file mode 100644 index 0000000000..71f2f1bc6e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form @@ -0,0 +1,114 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java new file mode 100644 index 0000000000..c6b6b721a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -0,0 +1,635 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.corecomponents; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.gstreamer.ClockTime; +import org.gstreamer.Gst; +import org.gstreamer.GstException; +import org.gstreamer.State; +import org.gstreamer.elements.PlayBin2; +import org.gstreamer.elements.RGBDataSink; +import org.gstreamer.swing.VideoComponent; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.util.Cancellable; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Video viewer part of the Media View layered pane + */ +@ServiceProviders(value = { + @ServiceProvider(service = FrameCapture.class) +}) +public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { + + private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); + private boolean gstInited; + //frame capture + private BufferedImage currentImage = null; + private static final long MIN_FRAME_INTERVAL_MILLIS = 500; + //playback + private long durationMillis = 0; + private VideoProgressWorker videoProgressWorker; + private int totalHours, totalMinutes, totalSeconds; + private volatile PlayBin2 gstPlaybin2; + private VideoComponent gstVideoComponent; + private boolean autoTracking = false; // true if the slider is moving automatically + private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player + private AbstractFile currentFile; + + /** + * Creates new form MediaViewVideoPanel + */ + public MediaViewVideoPanel() { + initComponents(); + customizeComponents(); + } + + public JButton getPauseButton() { + return pauseButton; + } + + public JLabel getProgressLabel() { + return progressLabel; + } + + public JSlider getProgressSlider() { + return progressSlider; + } + + public JPanel getVideoPanel() { + return videoPanel; + } + + public VideoComponent getVideoComponent() { + return gstVideoComponent; + } + + public boolean isInited() { + return gstInited; + } + + private void customizeComponents() { + initGst(); + + progressSlider.setEnabled(false); // disable slider; enable after user plays vid + + if (gstInited) { + progressSlider.addChangeListener(new ChangeListener() { + /** + * Should always try to synchronize any call to + * progressSlider.setValue() to avoid a different thread + * changing playbin while stateChanged() is processing + */ + @Override + public void stateChanged(ChangeEvent e) { + int time = progressSlider.getValue(); + synchronized (playbinLock) { + if (gstPlaybin2 != null && !autoTracking) { + State orig = gstPlaybin2.getState(); + gstPlaybin2.pause(); + gstPlaybin2.seek(ClockTime.fromMillis(time)); + gstPlaybin2.setState(orig); + } + } + } + }); + + gstVideoComponent = new VideoComponent(); + + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(gstVideoComponent); + + videoPanel.setVisible(true); + } + } + + private boolean initGst() { + try { + logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); + Gst.init(); + gstInited = true; + } catch (GstException e) { + gstInited = false; + logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e); + MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. " + + " Video and audio viewing will be disabled. ", + e.getMessage()); + return false; + } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { + gstInited = false; + logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e); + MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. " + + " Video and audio viewing will be disabled. ", + e.getMessage()); + return false; + } + + return true; + } + + /** + * Initialize all the necessary vars to play a video/audio file. + * + * @param file video file to play + * @param dims dimension of the parent window + */ + void setupVideo(final AbstractFile file, final Dimension dims) { + currentFile = file; + final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC); + if (deleted) { + infoLabel.setText("Playback of deleted videos is not supported, use an external player. "); + return; + } else { + try { + infoLabel.setText(file.getUniquePath()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Cannot get unique path of video file"); + } + } + + java.io.File ioFile = getJFile(file); + + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + gstPlaybin2.dispose(); + } + gstPlaybin2 = new PlayBin2("VideoPlayer"); + gstPlaybin2.setVideoSink(gstVideoComponent.getElement()); + + videoPanel.repaint(); + + gstPlaybin2.setInputFile(ioFile); + gstPlaybin2.setState(State.READY); + } + //setComponentsVisibility(true); + } + + void reset() { + currentFile = null; + // get rid of any existing videoProgressWorker thread + if (videoProgressWorker != null) { + videoProgressWorker.cancel(true); + videoProgressWorker = null; + } + + + // reset the progress label text on the event dispatch thread + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressLabel.setText(""); + infoLabel.setText(""); + + } + }); + + + if (!isInited()) { + return; + } + + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + if (gstPlaybin2.isPlaying()) { + gstPlaybin2.stop(); + } + gstPlaybin2.setState(State.NULL); + if (gstPlaybin2.getState().equals(State.NULL)) { + gstPlaybin2.dispose(); + } + gstPlaybin2 = null; + } + //videoComponent.setBackground(Color.BLACK); + //videoComponent.repaint(); + + + //videoPanel.repaint(); + } + } + + private java.io.File getJFile(AbstractFile file) { + // Get the temp folder path of the case + String tempPath = Case.getCurrentCase().getTempDirectory(); + String name = file.getName(); + int extStart = name.lastIndexOf("."); + String ext = ""; + if (extStart != -1) { + ext = name.substring(extStart, name.length()).toLowerCase(); + } + tempPath = tempPath + java.io.File.separator + file.getId() + ext; + + java.io.File tempFile = new java.io.File(tempPath); + return tempFile; + } + + @Override + public List captureFrames(java.io.File file, int numFrames) { + + List frames = new ArrayList<>(); + + if (!isInited()) { + return frames; + } + + RGBDataSink.Listener listener1 = new RGBDataSink.Listener() { + @Override + public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) { + BufferedImage curImage = new BufferedImage(w, h, + BufferedImage.TYPE_INT_ARGB); + curImage.setRGB(0, 0, w, h, rgbPixels.array(), 0, w); + currentImage = curImage; + } + }; + + // set up a PlayBin2 object + RGBDataSink videoSink = new RGBDataSink("rgb", listener1); + PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); + playbin.setInputFile(file); + playbin.setVideoSink(videoSink); + + // this is necessary to get a valid duration value + playbin.play(); + playbin.pause(); + playbin.getState(); + + // get the duration of the video + TimeUnit unit = TimeUnit.MILLISECONDS; + long myDurationMillis = playbin.queryDuration(unit); + if (myDurationMillis <= 0) { + return frames; + } + + // create a list of timestamps at which to get frames + int numFramesToGet = numFrames; + long frameInterval = myDurationMillis / numFrames; + if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { + numFramesToGet = 1; + } + + // for each timeStamp, grap a frame + for (int i = 0; i < numFramesToGet; ++i) { + long timeStamp = i * frameInterval; + + playbin.pause(); + playbin.getState(); + + currentImage = null; + if (!playbin.seek(timeStamp, unit)) { + logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); + } + playbin.play(); + + while (currentImage == null) { + System.out.flush(); // not sure why this is needed + } + + playbin.stop(); + + frames.add(new VideoFrame(currentImage, timeStamp)); + } + + return frames; + } + + /** + * 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() { + + videoPanel = new javax.swing.JPanel(); + controlPanel = new javax.swing.JPanel(); + pauseButton = new javax.swing.JButton(); + progressSlider = new javax.swing.JSlider(); + progressLabel = new javax.swing.JLabel(); + infoLabel = new javax.swing.JLabel(); + + javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); + videoPanel.setLayout(videoPanelLayout); + videoPanelLayout.setHorizontalGroup( + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + videoPanelLayout.setVerticalGroup( + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 146, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); + controlPanel.setLayout(controlPanelLayout); + controlPanelLayout.setHorizontalGroup( + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + controlPanelLayout.setVerticalGroup( + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 36, Short.MAX_VALUE) + ); + + org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N + pauseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pauseButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(pauseButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 359, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressLabel)) + .addGroup(layout.createSequentialGroup() + .addComponent(infoLabel) + .addGap(0, 0, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pauseButton) + .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(progressLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(infoLabel)) + .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + ); + }// //GEN-END:initComponents + + private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed + synchronized (playbinLock) { + State state = gstPlaybin2.getState(); + if (state.equals(State.PLAYING)) { + gstPlaybin2.pause(); + pauseButton.setText("►"); + gstPlaybin2.setState(State.PAUSED); + } else if (state.equals(State.PAUSED)) { + gstPlaybin2.play(); + pauseButton.setText("||"); + gstPlaybin2.setState(State.PLAYING); + } else if (state.equals(State.READY)) { + ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); + em.execute(); + em.getExtractedBytes(); + } + } + }//GEN-LAST:event_pauseButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel controlPanel; + private javax.swing.JLabel infoLabel; + private javax.swing.JButton pauseButton; + private javax.swing.JLabel progressLabel; + private javax.swing.JSlider progressSlider; + private javax.swing.JPanel videoPanel; + // End of variables declaration//GEN-END:variables + + private class VideoProgressWorker extends SwingWorker { + + private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; + private long millisElapsed = 0; + private final long INTER_FRAME_PERIOD_MS = 20; + private final long END_TIME_MARGIN_MS = 50; + + private boolean isPlayBinReady() { + synchronized (playbinLock) { + return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL); + } + } + + private void resetVideo() { + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + gstPlaybin2.stop(); + gstPlaybin2.setState(State.READY); // ready to be played again + } + } + pauseButton.setText("►"); + progressSlider.setValue(0); + + String durationStr = String.format(durationFormat, 0, 0, 0, + totalHours, totalMinutes, totalSeconds); + progressLabel.setText(durationStr); + } + + /** + * @return true while millisElapsed is greater than END_TIME_MARGIN_MS + * from durationMillis. This is used to indicate when the video has + * ended because for some videos the time elapsed never becomes equal to + * the reported duration of the video. + */ + private boolean hasNotEnded() { + return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; + } + + @Override + protected Object doInBackground() throws Exception { + + // enable the slider + progressSlider.setEnabled(true); + + int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; + ClockTime pos = null; + while (hasNotEnded() && isPlayBinReady() && !isCancelled()) { + + synchronized (playbinLock) { + pos = gstPlaybin2.queryPosition(); + } + millisElapsed = pos.toMillis(); + + // pick out the elapsed hours, minutes, seconds + long secondsElapsed = millisElapsed / 1000; + elapsedHours = (int) secondsElapsed / 3600; + secondsElapsed -= elapsedHours * 3600; + elapsedMinutes = (int) secondsElapsed / 60; + secondsElapsed -= elapsedMinutes * 60; + elapsedSeconds = (int) secondsElapsed; + + String durationStr = String.format(durationFormat, + elapsedHours, elapsedMinutes, elapsedSeconds, + totalHours, totalMinutes, totalSeconds); + + progressLabel.setText(durationStr); + autoTracking = true; + progressSlider.setValue((int) millisElapsed); + autoTracking = false; + + try { + Thread.sleep(INTER_FRAME_PERIOD_MS); + } catch (InterruptedException ex) { + break; + } + } + + // disable the slider + progressSlider.setEnabled(false); + + resetVideo(); + + return null; + } + } //end class progress worker + + /* Thread that extracts and plays a file */ + private class ExtractMedia extends SwingWorker { + + private ProgressHandle progress; + boolean success = false; + private AbstractFile sFile; + private java.io.File jFile; + private String duration; + private String position; + private long extractedBytes; + + ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { + this.sFile = sFile; + this.jFile = jFile; + } + + public long getExtractedBytes() { + return extractedBytes; + } + + @Override + protected Object doInBackground() throws Exception { + success = false; + progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() { + @Override + public boolean cancel() { + return ExtractMedia.this.cancel(true); + } + }); + progressLabel.setText("Buffering... "); + progress.start(); + progress.switchToDeterminate(100); + try { + extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error buffering file", ex); + } + success = true; + return null; + } + + /* clean up or start the worker threads */ + @Override + protected void done() { + try { + super.get(); //block and get all exceptions thrown while doInBackground() + } catch (CancellationException ex) { + logger.log(Level.INFO, "Media buffering was canceled."); + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Media buffering was interrupted."); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); + } finally { + progress.finish(); + if (!this.isCancelled()) { + playMedia(); + } + } + } + + void playMedia() { + if (jFile == null || !jFile.exists()) { + progressLabel.setText("Error buffering file"); + return; + } + ClockTime dur = null; + synchronized (playbinLock) { + gstPlaybin2.play(); // must play, then pause and get state to get duration. + gstPlaybin2.pause(); + gstPlaybin2.getState(); + dur = gstPlaybin2.queryDuration(); + } + duration = dur.toString(); + durationMillis = dur.toMillis(); + + // pick out the total hours, minutes, seconds + long durationSeconds = (int) durationMillis / 1000; + totalHours = (int) durationSeconds / 3600; + durationSeconds -= totalHours * 3600; + totalMinutes = (int) durationSeconds / 60; + durationSeconds -= totalMinutes * 60; + totalSeconds = (int) durationSeconds; + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressSlider.setMaximum((int) durationMillis); + progressSlider.setMinimum(0); + + synchronized (playbinLock) { + gstPlaybin2.play(); + } + pauseButton.setText("||"); + videoProgressWorker = new VideoProgressWorker(); + videoProgressWorker.execute(); + } + }); + } + } +} From c55419c4672c9f8114879bcd6ebcd405899b7c88 Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 21 Mar 2013 23:20:45 -0400 Subject: [PATCH 05/25] update bundle --- .../org/sleuthkit/autopsy/corecomponents/Bundle.properties | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 7429c69b37..82b35ed90b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -76,8 +76,6 @@ DataContentViewerArtifact.selectAllMenuItem.text=Select All DataContentViewerArtifact.pageLabel.text=Result: AdvancedConfigurationDialog.applyButton.text=OK -DataContentViewerMedia.pauseButton.text=\u25ba -DataContentViewerMedia.progressLabel.text= DataContentViewerString.goToPageLabel.text=Go to Page: DataContentViewerString.goToPageTextField.text= DataContentViewerHex.goToPageTextField.text= @@ -104,3 +102,7 @@ AdvancedConfigurationDialog.cancelButton.text=Cancel DataResultPanel.directoryTablePath.text=directoryPath DataResultPanel.numberMatchLabel.text=0 DataResultPanel.matchLabel.text=Results +MediaViewVideoPanel.pauseButton.text=\u25ba +MediaViewVideoPanel.progressLabel.text=00:00 +DataContentViewerMedia.AccessibleContext.accessibleDescription= +MediaViewVideoPanel.infoLabel.text=info From 2fb5aec2fa7e60afccb5eb848fabd12778bda4ae Mon Sep 17 00:00:00 2001 From: adam-m Date: Fri, 22 Mar 2013 01:08:49 -0400 Subject: [PATCH 06/25] video player - fix resetting after refactor --- .../DataContentViewerMedia.java | 12 ++---- .../corecomponents/MediaViewVideoPanel.java | 39 +++++++++++-------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index 8aadc0ec65..b148dd2897 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -81,13 +81,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo IMAGES[i] = "." + suffix; } - add(imagePanel, IMAGE_VIEWER_LAYER); add(videoPanel, VIDEO_VIEWER_LAYER); switchPanels(false); - - + } /** @@ -107,8 +105,6 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public void setNode(Node selectedNode) { - - videoPanel.reset(); if (selectedNode == null) { return; @@ -124,8 +120,8 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } else { lastFile = file; } - - + + videoPanel.reset(); final Dimension dims = DataContentViewerMedia.this.getSize(); @@ -154,8 +150,6 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } } - - @Override public String getTitle() { return "Media View"; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index c6b6b721a3..da8795afae 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -140,12 +140,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } }); - gstVideoComponent = new VideoComponent(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(gstVideoComponent); - - videoPanel.setVisible(true); } } @@ -195,6 +190,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt java.io.File ioFile = getJFile(file); + gstVideoComponent = new VideoComponent(); synchronized (playbinLock) { if (gstPlaybin2 != null) { gstPlaybin2.dispose(); @@ -202,29 +198,30 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt gstPlaybin2 = new PlayBin2("VideoPlayer"); gstPlaybin2.setVideoSink(gstVideoComponent.getElement()); - videoPanel.repaint(); + videoPanel.removeAll(); + + + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(gstVideoComponent); + + + videoPanel.setVisible(true); + //videoPanel.repaint(); gstPlaybin2.setInputFile(ioFile); gstPlaybin2.setState(State.READY); } - //setComponentsVisibility(true); + } void reset() { - currentFile = null; - // get rid of any existing videoProgressWorker thread - if (videoProgressWorker != null) { - videoProgressWorker.cancel(true); - videoProgressWorker = null; - } - // reset the progress label text on the event dispatch thread SwingUtilities.invokeLater(new Runnable() { @Override public void run() { progressLabel.setText(""); - infoLabel.setText(""); + // infoLabel.setText(""); } }); @@ -245,12 +242,21 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } gstPlaybin2 = null; } + gstVideoComponent = null; //videoComponent.setBackground(Color.BLACK); //videoComponent.repaint(); //videoPanel.repaint(); } + + // get rid of any existing videoProgressWorker thread + if (videoProgressWorker != null) { + videoProgressWorker.cancel(true); + videoProgressWorker = null; + } + + currentFile = null; } private java.io.File getJFile(AbstractFile file) { @@ -465,6 +471,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt if (gstPlaybin2 != null) { gstPlaybin2.stop(); gstPlaybin2.setState(State.READY); // ready to be played again + gstPlaybin2.getState(); //NEW } } pauseButton.setText("►"); @@ -602,7 +609,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt synchronized (playbinLock) { gstPlaybin2.play(); // must play, then pause and get state to get duration. gstPlaybin2.pause(); - gstPlaybin2.getState(); + State state = gstPlaybin2.getState(); dur = gstPlaybin2.queryDuration(); } duration = dur.toString(); From 1a2fda94227096a09e5ce7396de5be4a21185e16 Mon Sep 17 00:00:00 2001 From: adam-m Date: Fri, 22 Mar 2013 10:23:41 -0400 Subject: [PATCH 07/25] center picture in image view, other minor adjustments in media view --- .../DataContentViewerMedia.java | 3 +- .../corecomponents/MediaViewImagePanel.form | 1 + .../corecomponents/MediaViewImagePanel.java | 18 ++++++------ .../corecomponents/MediaViewVideoPanel.form | 28 +++++++++++-------- .../corecomponents/MediaViewVideoPanel.java | 27 ++++++++++-------- 5 files changed, 43 insertions(+), 34 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index b148dd2897..9726c9481c 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -42,7 +42,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { private String[] IMAGES; // use javafx supported - private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg"}; + private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); @@ -107,6 +107,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo public void setNode(Node selectedNode) { if (selectedNode == null) { + videoPanel.reset(); return; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form index a13f4244a5..780bc7fcd1 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form @@ -11,6 +11,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index 4ce2d1b744..a0f8623481 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -31,6 +31,7 @@ import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; import javax.imageio.ImageIO; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; @@ -80,7 +81,7 @@ public class MediaViewImagePanel extends javax.swing.JPanel { add(fxPanel); //TODO - // setVisible(true); + // setVisible(true); } }); @@ -141,25 +142,24 @@ public class MediaViewImagePanel extends javax.swing.JPanel { return; } - // simple displays ImageView the image as is + BorderPane borderpane = new BorderPane(); + borderpane.setCenter(fxImageView); fxImageView.setImage(fxImage); fxImageView.setFitWidth(dims.getWidth()); fxImageView.setFitHeight(dims.getHeight()); - Group fxRoot = new Group(); + //Group fxRoot = new Group(); //Scene fxScene = new Scene(fxRoot, dims.getWidth(), dims.getHeight(), javafx.scene.paint.Color.BLACK); - Scene fxScene = new Scene(fxRoot, javafx.scene.paint.Color.BLACK); - fxRoot.getChildren().add(fxImageView); + Scene fxScene = new Scene(borderpane, javafx.scene.paint.Color.BLACK); + // borderpane.getChildren().add(fxImageView); + fxPanel.setScene(fxScene); - //TODO - //setVisible(true); + } }); - - } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form index 71f2f1bc6e..ef6001605b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form @@ -18,17 +18,19 @@ - - - - - - + + + + + + + + + - - - - + + + @@ -36,13 +38,15 @@ - - + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index da8795afae..b804222ab4 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -118,6 +118,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt initGst(); progressSlider.setEnabled(false); // disable slider; enable after user plays vid + progressSlider.setValue(0); if (gstInited) { progressSlider.addChangeListener(new ChangeListener() { @@ -182,7 +183,9 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt return; } else { try { - infoLabel.setText(file.getUniquePath()); + String path = file.getUniquePath(); + infoLabel.setText(path); + infoLabel.setToolTipText(path); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Cannot get unique path of video file"); } @@ -203,10 +206,8 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.add(gstVideoComponent); - videoPanel.setVisible(true); - //videoPanel.repaint(); gstPlaybin2.setInputFile(ioFile); gstPlaybin2.setState(State.READY); @@ -398,28 +399,30 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addComponent(pauseButton) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(pauseButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 329, Short.MAX_VALUE)) + .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 359, Short.MAX_VALUE) + .addComponent(progressLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressLabel)) - .addGroup(layout.createSequentialGroup() - .addComponent(infoLabel) - .addGap(0, 0, Short.MAX_VALUE)) + .addGap(32, 32, 32)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(0, 0, 0) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(pauseButton) .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(progressLabel)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressLabel))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(infoLabel)) .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) From 1251c538dfc44bc5d8245bd9be884e07dcb29220 Mon Sep 17 00:00:00 2001 From: adam-m Date: Fri, 22 Mar 2013 11:07:46 -0400 Subject: [PATCH 08/25] improve case open file dialog filter to show the aut extension first, better message if could not open. --- .../sleuthkit/autopsy/casemodule/Case.java | 16 ++++--- .../autopsy/casemodule/CaseOpenAction.java | 43 ++++++++++--------- .../autopsy/casemodule/GeneralFilter.java | 5 ++- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index d1615f6c48..c39f10d476 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -116,6 +116,8 @@ public class Case { private static Case currentCase = null; private Services services; private static final Logger logger = Logger.getLogger(Case.class.getName()); + static final String CASE_EXTENSION = "aut"; + static final String CASE_DOT_EXTENSION = "." + CASE_EXTENSION; /** * Constructor for the Case class @@ -202,7 +204,7 @@ public class Case { static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException { logger.log(Level.INFO, "Creating new case.\ncaseDir: {0}\ncaseName: {1}", new Object[]{caseDir, caseName}); - String configFilePath = caseDir + File.separator + caseName + ".aut"; + String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION; XMLCaseManagement xmlcm = new XMLCaseManagement(); xmlcm.create(caseDir, caseName, examiner, caseNumber); // create a new XML config file @@ -260,7 +262,12 @@ public class Case { // close the previous case if there's any CaseCloseAction closeCase = SystemAction.get(CaseCloseAction.class); closeCase.actionPerformed(null); - throw new CaseActionException("Error opening the case", ex); + if (!configFilePath.endsWith(CASE_DOT_EXTENSION)) { + throw new CaseActionException("Check that you selected the correct case file (usually with " + + CASE_DOT_EXTENSION + " extension)", ex); + } else { + throw new CaseActionException("Error opening the case", ex); + } } } @@ -562,7 +569,6 @@ public class Case { } } - /** * Get absolute module output directory path where modules should save their * permanent data The directory is a subdirectory of this case dir. @@ -916,7 +922,7 @@ public class Case { Frame f = WindowManager.getDefault().getMainWindow(); f.setTitle(Case.getAppName()); // set the window name to just application name - + //try to force gc to happen System.gc(); System.gc(); @@ -924,7 +930,7 @@ public class Case { //log memory usage after case changed logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo()); - + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 6f5406dbbf..41b1d8eb24 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -16,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.casemodule; import java.awt.Component; @@ -27,6 +26,8 @@ import java.util.Collections; import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JOptionPane; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Logger; @@ -36,33 +37,35 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ @ServiceProvider(service = CaseOpenAction.class) public final class CaseOpenAction implements ActionListener { + private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName()); private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; + private final JFileChooser fc = new JFileChooser(); + private FileFilter autFilter; - JFileChooser fc = new JFileChooser(); - GeneralFilter autFilter = new GeneralFilter(Collections.singletonList(".aut"), "AUTOPSY File (*.aut)"); - - /** The constructor */ + /** + * The constructor + */ public CaseOpenAction() { + autFilter = new FileNameExtensionFilter(org.sleuthkit.autopsy.coreutils.Version.getName() + + " Case File ( " + Case.CASE_DOT_EXTENSION + ")", + Case.CASE_EXTENSION); fc.setDragEnabled(false); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(false); - //fc.setAcceptAllFileFilterUsed(false); - fc.addChoosableFileFilter(autFilter); - //fc.addChoosableFileFilter(fc.getAcceptAllFileFilter()); - try{ - if(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE) != null) - fc.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); - } - catch(Exception e){ - + fc.setFileFilter(autFilter); + try { + if (ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE) != null) { + fc.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); + } + } catch (Exception e) { } } /** * Pop-up the File Chooser to open the existing case (.aut file) - * - * @param e the action event + * + * @param e the action event */ @Override public void actionPerformed(ActionEvent e) { @@ -70,6 +73,7 @@ public final class CaseOpenAction implements ActionListener { int retval = fc.showOpenDialog((Component) e.getSource()); + if (retval == JFileChooser.APPROVE_OPTION) { String path = fc.getSelectedFile().getPath(); String dirPath = fc.getSelectedFile().getParent(); @@ -89,11 +93,10 @@ public final class CaseOpenAction implements ActionListener { try { Case.open(path); // open the case } catch (CaseActionException ex) { - JOptionPane.showMessageDialog(null, "Error: could not open the case in folder " + path - + " due to unexpected error" - , "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, "Error: could not open the case in folder " + path + + ": " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); logger.log(Level.WARNING, "Error opening case in folder " + path, ex); - + StartupWindow.getInstance().open(); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java index 16620d9a07..b550a0a275 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java @@ -28,10 +28,11 @@ import javax.swing.filechooser.FileFilter; */ public class GeneralFilter extends FileFilter{ - List extensions; - String desc; + private List extensions; + private String desc; public GeneralFilter(List ext, String desc){ + super(); this.extensions = ext; this.desc = desc; } From b0e5ff5cbb67326c9c2627af47416a56f56fe123 Mon Sep 17 00:00:00 2001 From: adam-m Date: Fri, 22 Mar 2013 11:16:47 -0400 Subject: [PATCH 09/25] improve image loading to hide temporarily shown previus image when switching between image->video->image --- .../autopsy/corecomponents/MediaViewImagePanel.form | 5 +++++ .../autopsy/corecomponents/MediaViewImagePanel.java | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form index 780bc7fcd1..1a3af391a6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form @@ -1,6 +1,11 @@
+ + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index a0f8623481..cafed20be1 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -102,6 +102,9 @@ public class MediaViewImagePanel extends javax.swing.JPanel { void showImageFx(final AbstractFile file, final Dimension dims) { final String fileName = file.getName(); + //hide the panel during loading/transformations + fxPanel.setVisible(false); + // load the image PlatformImpl.runLater(new Runnable() { @Override @@ -142,6 +145,7 @@ public class MediaViewImagePanel extends javax.swing.JPanel { return; } + //use border pane to center the image in the scene BorderPane borderpane = new BorderPane(); borderpane.setCenter(fxImageView); @@ -156,6 +160,9 @@ public class MediaViewImagePanel extends javax.swing.JPanel { // borderpane.getChildren().add(fxImageView); fxPanel.setScene(fxScene); + + //show the panel after fully loaded + fxPanel.setVisible(true); } }); @@ -171,6 +178,7 @@ public class MediaViewImagePanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { + setBackground(new java.awt.Color(0, 0, 0)); setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables From a9b6167381bcfd65fb691b9b15618d39b06eed7d Mon Sep 17 00:00:00 2001 From: adam-m Date: Fri, 22 Mar 2013 12:30:39 -0400 Subject: [PATCH 10/25] improve video widget layout --- .../corecomponents/MediaViewVideoPanel.form | 114 +++++++++--------- 1 file changed, 54 insertions(+), 60 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form index ef6001605b..2c6bd469f8 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form @@ -16,43 +16,17 @@ - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - + + + @@ -68,7 +42,7 @@ - + @@ -78,41 +52,61 @@ - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - From 187f24302e212bfa8b5a73eca59b2b7771f48c30 Mon Sep 17 00:00:00 2001 From: adam-m Date: Fri, 22 Mar 2013 13:55:50 -0400 Subject: [PATCH 11/25] video player cleaner switching between deleted and not deleted videos --- .../corecomponents/MediaViewVideoPanel.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index b804222ab4..dc2f54bde6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -180,12 +180,17 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC); if (deleted) { infoLabel.setText("Playback of deleted videos is not supported, use an external player. "); + videoPanel.removeAll(); + pauseButton.setEnabled(false); + progressSlider.setEnabled(false); return; } else { try { String path = file.getUniquePath(); infoLabel.setText(path); infoLabel.setToolTipText(path); + pauseButton.setEnabled(true); + progressSlider.setEnabled(true); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Cannot get unique path of video file"); } @@ -193,7 +198,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt java.io.File ioFile = getJFile(file); - gstVideoComponent = new VideoComponent(); + gstVideoComponent = new VideoComponent(); synchronized (playbinLock) { if (gstPlaybin2 != null) { gstPlaybin2.dispose(); @@ -202,11 +207,11 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt gstPlaybin2.setVideoSink(gstVideoComponent.getElement()); videoPanel.removeAll(); - + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.add(gstVideoComponent); - + videoPanel.setVisible(true); gstPlaybin2.setInputFile(ioFile); @@ -222,7 +227,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt @Override public void run() { progressLabel.setText(""); - // infoLabel.setText(""); + // infoLabel.setText(""); } }); @@ -243,15 +248,15 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } gstPlaybin2 = null; } - gstVideoComponent = null; + gstVideoComponent = null; //videoComponent.setBackground(Color.BLACK); //videoComponent.repaint(); //videoPanel.repaint(); } - - // get rid of any existing videoProgressWorker thread + + // get rid of any existing videoProgressWorker thread if (videoProgressWorker != null) { videoProgressWorker.cancel(true); videoProgressWorker = null; From 7375c9283cc8f698997bdbf09bd996c96132d75c Mon Sep 17 00:00:00 2001 From: adam-m Date: Fri, 22 Mar 2013 15:46:21 -0400 Subject: [PATCH 12/25] Fix "View in New Window" action after the recent fix with duplicate content viewers --- .../DataContentTopComponent.form | 21 +++---------------- .../DataContentTopComponent.java | 20 +++++++----------- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form index 1bad719e67..cef977f0c2 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form @@ -11,25 +11,10 @@ +
- - - - - - - - - - - + + - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java index fde97d8b7a..4091980ab2 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java @@ -47,6 +47,8 @@ public final class DataContentTopComponent extends TopComponent implements DataC private static DataContentTopComponent defaultInstance; // set to true if this is the TC that always stays open and is the default place to display content private boolean isDefault; + // the content panel holding tabs with content viewers + private final DataContentPanel dataContentPanel; // contains a list of the undocked TCs private static ArrayList newWindowList = new ArrayList(); @@ -60,6 +62,10 @@ public final class DataContentTopComponent extends TopComponent implements DataC setToolTipText(TOOLTIP_TEXT); this.isDefault = isDefault; + + dataContentPanel = new DataContentPanel(isDefault); + add(dataContentPanel); + putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.valueOf(isDefault)); // prevent option to close compoment in GUI logger.log(Level.INFO, "Created DataContentTopComponent instance: " + this); } @@ -90,21 +96,9 @@ public final class DataContentTopComponent extends TopComponent implements DataC // //GEN-BEGIN:initComponents private void initComponents() { - dataContentPanel = new DataContentPanel(true); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 585, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 435, Short.MAX_VALUE) - ); + setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private org.sleuthkit.autopsy.corecomponents.DataContentPanel dataContentPanel; // End of variables declaration//GEN-END:variables /** From 8bd585b90981bba2b207b3f655bf9d2163aa0634 Mon Sep 17 00:00:00 2001 From: Tim McIver Date: Fri, 22 Mar 2013 15:54:02 -0400 Subject: [PATCH 13/25] Added code to propagate changes to MessageTableModel to the IngestMessagePanel which then propagates this change to listeners via its PropertyChangeSupport member. There is one problem: at the end of the RecentActivity ingest the message inbox icon shows two new messages when there should be only one. --- .../autopsy/ingest/IngestMessagePanel.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index a31b5bac08..7dcce73dae 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java @@ -24,6 +24,7 @@ import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.text.DateFormat; @@ -36,12 +37,15 @@ import java.util.List; import java.util.Map; import java.util.logging.Level; import javax.swing.JLabel; +import javax.swing.JPanel; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; @@ -52,7 +56,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; * Notification window showing messages from modules to user * */ -class IngestMessagePanel extends javax.swing.JPanel { +class IngestMessagePanel extends JPanel implements TableModelListener { private MessageTableModel tableModel; private MessageTableRenderer renderer; @@ -71,6 +75,7 @@ class IngestMessagePanel extends javax.swing.JPanel { public IngestMessagePanel(IngestMessageMainPanel mainPanel) { this.mainPanel = mainPanel; tableModel = new MessageTableModel(); + tableModel.addTableModelListener(this); initComponents(); customizeComponents(); } @@ -316,6 +321,12 @@ class IngestMessagePanel extends javax.swing.JPanel { messagePcs.firePropertyChange(TOOL_TIP_TEXT_KEY, origMsgGroups, tableModel.getNumberUnreadGroups()); } + @Override + public void tableChanged(TableModelEvent e) { + int newMessages = tableModel.getNumberNewMessages(); + messagePcs.firePropertyChange(new PropertyChangeEvent(tableModel, "NewMessagesChanged", -1, newMessages)); + } + private class MessageTableModel extends AbstractTableModel { private String[] columnNames = new String[]{"Module", "Num", "New?", "Subject", "Timestamp"}; @@ -355,6 +366,17 @@ class IngestMessagePanel extends javax.swing.JPanel { for (TableEntry entry : messageData) { entry.hasBeenSeen(true); } + fireTableChanged(new TableModelEvent(this)); + } + + public int getNumberNewMessages() { + int newMessages = 0; + for (TableEntry entry : messageData) { + if (!entry.hasBeenSeen()) { + ++newMessages; + } + } + return newMessages; } synchronized int getNumberGroups() { From d4f74de0147f3370f19fdcb9462241b3fc9c8e5d Mon Sep 17 00:00:00 2001 From: adam Date: Sat, 23 Mar 2013 19:30:18 -0300 Subject: [PATCH 14/25] Update BUILDING.txt --- BUILDING.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/BUILDING.txt b/BUILDING.txt index b8295269e9..7cba4cdc1e 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -1,4 +1,4 @@ - Last Updated: Sept 28, 2012 + Last Updated: March 23, 2013 This file outlines what it takes to build Autopsy from source. @@ -14,6 +14,11 @@ STEPS: 1a) Download and install 32-bit version of JDK version 1.7 (32-bit is currently needed even if you have a 64-bit system). +Autopsy has been used and tested with Oracle JavaSE and the included JavaFX support +(http://www.oracle.com/technetwork/java/javase/downloads/index.html). + +OpenJDK and OpenJFX might work, but they are not fully tested with Autopsy. + 1b) Ensure that JDK_HOME is set to the root JDK directory. 1c) (optional) Download and install Netbeans IDE (http://netbeans.org/) @@ -28,7 +33,7 @@ need to set JRE_HOME to the root JRE directory. 2) Get Sleuth Kit Setup -2a) Download and build the release version of Libewf2 (20120304 or +2a) Download and build the release version of Libewf2 (20130119 or later). All you need is the dll file. Note that you will get a launching error if you use libewf 1. - http://sourceforge.net/projects/libewf/ From 99a56adc9293ab5375d6b5fce8e6b6c63364f5c4 Mon Sep 17 00:00:00 2001 From: adam-m Date: Mon, 25 Mar 2013 09:35:51 -0400 Subject: [PATCH 15/25] exif module: send new data event more sparsely to reduce number of dir tree refreshes (temp workaround) --- .../autopsy/exifparser/ExifParserFileIngestModule.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java index 26ea16c14d..1339aca78d 100644 --- a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java +++ b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java @@ -78,6 +78,8 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil private static final Logger logger = Logger.getLogger(ExifParserFileIngestModule.class.getName()); private static ExifParserFileIngestModule defaultInstance = null; private static int messageId = 0; + + private int filesProcessed = 0; //file ingest modules require a private constructor //to ensure singleton instances @@ -164,7 +166,10 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil if(!attributes.isEmpty()) { BlackboardArtifact bba = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); bba.addAttributes(attributes); - services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); + ++filesProcessed; + if (filesProcessed %100 == 0) { + services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); + } } return IngestModuleAbstractFile.ProcessResult.OK; @@ -277,6 +282,8 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil public void init(IngestModuleInit initContext) { services = IngestServices.getDefault(); logger.log(Level.INFO, "init() " + this.toString()); + + filesProcessed = 0; } @Override From 80ff3c75826999ab88f474531d18669b76781924 Mon Sep 17 00:00:00 2001 From: adam-m Date: Mon, 25 Mar 2013 09:42:28 -0400 Subject: [PATCH 16/25] change base case dir dialog from save to select --- .../org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index 0ce1e09ddb..fd399a59ce 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -179,7 +179,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener{ //fc.setSelectedFile(new File("C:\\Program Files\\")); //disableTextField(fc); // disable all the text field on the file chooser - int returnValue = fc.showSaveDialog((Component)evt.getSource()); + int returnValue = fc.showDialog((Component)evt.getSource(), "Select"); if(returnValue == JFileChooser.APPROVE_OPTION){ String path = fc.getSelectedFile().getPath(); caseParentDirTextField.setText(path); // put the path to the textfield From 0e9d25767e817dca64f2449da71e0bc7dc6b876f Mon Sep 17 00:00:00 2001 From: adam-m Date: Mon, 25 Mar 2013 10:00:52 -0400 Subject: [PATCH 17/25] exif module: also send event at the end of all files --- .../autopsy/exifparser/ExifParserFileIngestModule.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java index 1339aca78d..481270217d 100644 --- a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java +++ b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java @@ -248,6 +248,10 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil public void complete() { logger.log(Level.INFO, "completed exif parsing " + this.toString()); + if (filesProcessed > 0) { + //send the final new data event + services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); + } //module specific cleanup due to completion here } From e8aae2c1a7a0b57e9fcb317bfc1a3aafc8b50532 Mon Sep 17 00:00:00 2001 From: adam-m Date: Mon, 25 Mar 2013 10:16:53 -0400 Subject: [PATCH 18/25] update comments --- .../sleuthkit/autopsy/datamodel/ExtractedContentChildren.java | 4 +++- .../org/sleuthkit/autopsy/directorytree/TagFileAction.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java index 94b65a9d35..2e233d22f0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java @@ -27,7 +27,6 @@ import org.sleuthkit.datamodel.SleuthkitCase; /** * - * @author dfickling */ public class ExtractedContentChildren extends ChildFactory { @@ -51,6 +50,9 @@ public class ExtractedContentChildren extends ChildFactory Date: Mon, 25 Mar 2013 10:23:40 -0400 Subject: [PATCH 19/25] video viewer: align progress label better --- .../corecomponents/MediaViewVideoPanel.form | 7 +- .../corecomponents/MediaViewVideoPanel.java | 68 +++++++++---------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form index 2c6bd469f8..ceab29b1e8 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form @@ -54,10 +54,11 @@ - - + + + @@ -71,7 +72,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index dc2f54bde6..1e0b890810 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -373,18 +373,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt ); videoPanelLayout.setVerticalGroup( videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 146, Short.MAX_VALUE) - ); - - javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); - controlPanel.setLayout(controlPanelLayout); - controlPanelLayout.setHorizontalGroup( - controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) - ); - controlPanelLayout.setVerticalGroup( - controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 36, Short.MAX_VALUE) + .addGap(0, 188, Short.MAX_VALUE) ); org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N @@ -398,39 +387,46 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N + javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); + controlPanel.setLayout(controlPanelLayout); + controlPanelLayout.setHorizontalGroup( + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createSequentialGroup() + .addComponent(pauseButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressLabel) + .addContainerGap()) + .addGroup(controlPanelLayout.createSequentialGroup() + .addComponent(infoLabel) + .addGap(0, 0, Short.MAX_VALUE)) + ); + controlPanelLayout.setVerticalGroup( + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createSequentialGroup() + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pauseButton) + .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(progressLabel, javax.swing.GroupLayout.Alignment.TRAILING)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(infoLabel)) + ); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(pauseButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 329, Short.MAX_VALUE)) - .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(32, 32, 32)) + .addComponent(videoPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(pauseButton) - .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressLabel))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(infoLabel)) - .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) ); }// //GEN-END:initComponents From f743dd138a2740f41b09fc15673343b176ef3e2d Mon Sep 17 00:00:00 2001 From: adam-m Date: Mon, 25 Mar 2013 10:24:51 -0400 Subject: [PATCH 20/25] update news --- NEWS.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/NEWS.txt b/NEWS.txt index 5b725ed82d..7994dd0d9d 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -13,12 +13,10 @@ Improvements: Bugfixes: - fixed memory leaks in "Add Image" -- fix bug with duplicate instances of content and result viewers hidden and possibly degrading performance +- The "media view" tab is inactive for deleted files (#165) - show error message in hex and string viewer if specific offset of a file could not be read. - file search actions not always enabled when new case is open. - fixed directory tree history being reset when tree is refreshed. -- The "media view" tab is inactive for deleted files (#165) - ---------------- VERSION 3.0.4 -------------- From 48f0ccefb5ace65ba090f6002e71cbe989fa1f75 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 25 Mar 2013 10:46:46 -0400 Subject: [PATCH 21/25] better log exception from javafx init --- Core/src/org/sleuthkit/autopsy/core/Installer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 584c1700e4..cc6b91e5f6 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -118,7 +118,7 @@ public class Installer extends ModuleInstall { final String details = " Some features will not be available. " + " Check that you have the right JRE installed (Sun JRE > 1.7.10). "; logger.log(Level.SEVERE, msg - + details); + + details, e); WindowManager.getDefault().invokeWhenUIReady(new Runnable() { @Override From 8329c186258857a5cef999a53ec32a8fb86bc5bb Mon Sep 17 00:00:00 2001 From: adam-m Date: Mon, 25 Mar 2013 11:16:40 -0400 Subject: [PATCH 22/25] Cleanly handle the case where javafx is not installed and components that depend on it at runtime (timeline, image viewer) cannot initialize --- .../org/sleuthkit/autopsy/core/Installer.java | 8 +++++ .../DataContentViewerMedia.java | 14 ++++++--- .../corecomponents/MediaViewImagePanel.java | 31 ++++++++++++++++--- .../sleuthkit/autopsy/timeline/Timeline.java | 19 +++++++++--- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index cc6b91e5f6..fc791743fc 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -101,6 +101,14 @@ public class Installer extends ModuleInstall { } + /** + * Check if JavaFx initialized + * @return false if java fx not initialized (classes coult not load), true if initialized + */ + public boolean isJavaFxInited() { + return this.javaFxInit; + } + private void initJavaFx() { //initialize java fx if exists try { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index 9726c9481c..5da28141a3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -51,6 +51,8 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo //UI private final MediaViewVideoPanel videoPanel; private final MediaViewImagePanel imagePanel; + private boolean videoPanelInited; + private boolean imagePanelInited; private static final String IMAGE_VIEWER_LAYER = "IMAGE"; private static final String VIDEO_VIEWER_LAYER = "VIDEO"; @@ -64,6 +66,8 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo videoPanel = new MediaViewVideoPanel(); imagePanel = new MediaViewImagePanel(); + videoPanelInited = videoPanel.isInited(); + imagePanelInited = imagePanel.isInited(); customizeComponents(); logger.log(Level.INFO, "Created MediaView instance: " + this); @@ -126,10 +130,10 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo final Dimension dims = DataContentViewerMedia.this.getSize(); - if (containsExt(file.getName(), IMAGES)) { + if (imagePanelInited && containsExt(file.getName(), IMAGES)) { imagePanel.showImageFx(file, dims); this.switchPanels(false); - } else if (videoPanel.isInited() + } else if (videoPanelInited && (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) { videoPanel.setupVideo(file, dims); @@ -185,7 +189,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo if (node == null) { return false; } - + AbstractFile file = node.getLookup().lookup(AbstractFile.class); if (file == null) { return false; @@ -198,11 +202,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo String name = file.getName().toLowerCase(); - if (containsExt(name, IMAGES)) { + if (imagePanelInited && containsExt(name, IMAGES)) { return true; } //for gstreamer formats, check if initialized first, then //support audio formats, and video formats - else if (videoPanel.isInited() + else if (videoPanelInited && videoPanel.isInited() && (containsExt(name, AUDIOS) || (containsExt(name, VIDEOS)))) { return true; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index cafed20be1..831cdb652e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -24,15 +24,17 @@ import java.awt.EventQueue; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.logging.Level; import javafx.embed.swing.JFXPanel; import javafx.embed.swing.SwingFXUtils; -import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javax.imageio.ImageIO; +import org.openide.modules.ModuleInstall; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; import org.sleuthkit.autopsy.coreutils.Logger; @@ -49,13 +51,30 @@ public class MediaViewImagePanel extends javax.swing.JPanel { private JFXPanel fxPanel; private ImageView fxImageView; private static final Logger logger = Logger.getLogger(MediaViewImagePanel.class.getName()); + private boolean fxInited = false; /** * Creates new form MediaViewImagePanel */ public MediaViewImagePanel() { initComponents(); - setupFx(); + + + + org.sleuthkit.autopsy.core.Installer coreInstaller = + ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false); + if (coreInstaller != null) { + fxInited = coreInstaller.isJavaFxInited(); + } + + + if (fxInited) { + setupFx(); + } + } + + public boolean isInited() { + return fxInited; } /** @@ -100,11 +119,15 @@ public class MediaViewImagePanel extends javax.swing.JPanel { * @param dims dimension of the parent window */ void showImageFx(final AbstractFile file, final Dimension dims) { + if (!fxInited) { + return; + } + final String fileName = file.getName(); //hide the panel during loading/transformations fxPanel.setVisible(false); - + // load the image PlatformImpl.runLater(new Runnable() { @Override @@ -160,7 +183,7 @@ public class MediaViewImagePanel extends javax.swing.JPanel { // borderpane.getChildren().add(fxImageView); fxPanel.setScene(fxScene); - + //show the panel after fully loaded fxPanel.setVisible(true); diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java index 4357430fba..4dca8da962 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java @@ -65,11 +65,8 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; -import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; @@ -78,6 +75,7 @@ import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; import org.openide.awt.ActionRegistration; import org.openide.modules.InstalledFileLocator; +import org.openide.modules.ModuleInstall; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; @@ -141,6 +139,19 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, private EventHandler fxMouseEnteredListener; private EventHandler fxMouseExitedListener; private SleuthkitCase skCase; + private boolean fxInited = false; + + public Timeline() { + super(); + + org.sleuthkit.autopsy.core.Installer coreInstaller = + ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false); + if (coreInstaller != null) { + fxInited = coreInstaller.isJavaFxInited(); + } + + setEnabled(fxInited); + } //Swing components and JavafX components don't play super well together //Swing components need to be initialized first, in the swing specific thread @@ -165,7 +176,7 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, @Override public void run() { //Making the main frame * - + mainFrame = new TimelineFrame(); mainFrame.setFrameName(Case.getCurrentCase().getName() + " - Autopsy Timeline (Beta)"); From e38489d82c448ba61406a23bee9052d0a46243f1 Mon Sep 17 00:00:00 2001 From: adam-m Date: Mon, 25 Mar 2013 12:22:03 -0400 Subject: [PATCH 23/25] fix enable timeline action only if fx enabled and case open --- Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java index 4dca8da962..c4fa6b3a52 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java @@ -150,7 +150,6 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, fxInited = coreInstaller.isJavaFxInited(); } - setEnabled(fxInited); } //Swing components and JavafX components don't play super well together @@ -1030,7 +1029,7 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, @Override public boolean isEnabled() { - return Case.isCaseOpen(); + return Case.isCaseOpen() && this.fxInited; } @Override From ecd2f1605f5b15d505063c95fd32225ad1e0b688 Mon Sep 17 00:00:00 2001 From: Tim McIver Date: Mon, 25 Mar 2013 15:22:18 -0400 Subject: [PATCH 24/25] Fixed bug causing the number of new messages displayed by the ingest inbox icon to be wrong. --- .../autopsy/ingest/IngestMessagePanel.java | 10 +++++---- .../autopsy/ingest/IngestMessagesToolbar.java | 22 ++++++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index 7dcce73dae..e8122c5903 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java @@ -69,7 +69,9 @@ class IngestMessagePanel extends JPanel implements TableModelListener { private volatile long totalMessages = 0; private static PropertyChangeSupport messagePcs = new PropertyChangeSupport(IngestMessagePanel.class); - static final String MESSAGE_CHANGE_EVT = "MESSAGE_CHANGE_EVT"; //number of unread messages changed + static final String TOTAL_NUM_MESSAGES_CHANGED = "TOTAL_NUM_MESSAGES_CHANGED"; // total number of messages changed + static final String MESSAGES_BOX_CLEARED = "MESSAGES_BOX_CLEARED"; // all messaged in inbox were cleared + static final String TOTAL_NUM_NEW_MESSAGES_CHANGED = "TOTAL_NUM_NEW_MESSAGES_CHANGED"; // total number of new messages changed /** Creates new form IngestMessagePanel */ public IngestMessagePanel(IngestMessageMainPanel mainPanel) { @@ -288,7 +290,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { ++totalMessages; final int newMsgUnreadUnique = tableModel.getNumberUnreadGroups(); - messagePcs.firePropertyChange(TOOL_TIP_TEXT_KEY, 0, newMsgUnreadUnique); + messagePcs.firePropertyChange(TOTAL_NUM_MESSAGES_CHANGED, 0, newMsgUnreadUnique); //update labels this.totalMessagesNameVal.setText(Long.toString(totalMessages)); @@ -306,7 +308,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { tableModel.clearMessages(); totalMessagesNameVal.setText("-"); totalUniqueMessagesNameVal.setText("-"); - messagePcs.firePropertyChange(TOOL_TIP_TEXT_KEY, origMsgGroups, 0); + messagePcs.firePropertyChange(MESSAGES_BOX_CLEARED, origMsgGroups, 0); } public synchronized int getMessagesCount() { @@ -324,7 +326,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { @Override public void tableChanged(TableModelEvent e) { int newMessages = tableModel.getNumberNewMessages(); - messagePcs.firePropertyChange(new PropertyChangeEvent(tableModel, "NewMessagesChanged", -1, newMessages)); + messagePcs.firePropertyChange(new PropertyChangeEvent(tableModel, TOTAL_NUM_NEW_MESSAGES_CHANGED, -1, newMessages)); } private class MessageTableModel extends AbstractTableModel { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java index 51167f8ef9..6d4bb2cb6c 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.ingest; import java.awt.BorderLayout; import java.awt.Color; -import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.beans.PropertyChangeEvent; @@ -110,13 +109,24 @@ public class IngestMessagesToolbar extends javax.swing.JPanel { this.setBorder(null); IngestMessagePanel.addPropertyChangeSupportListener(new PropertyChangeListener() { + + private int numNewMessages = 0; + @Override public void propertyChange(PropertyChangeEvent evt) { - final int numberMessages = (Integer) evt.getNewValue(); - //ingestMessagesButton.setText(Integer.toString(numberMessages)); - //repaint button - ingestMessagesButton.setMessages(numberMessages); - ingestMessagesButton.repaint(); + + String propName = evt.getPropertyName(); + int newNumNewMessages = numNewMessages; + if (propName.equals(IngestMessagePanel.MESSAGES_BOX_CLEARED) || + propName.equals(IngestMessagePanel.TOTAL_NUM_NEW_MESSAGES_CHANGED)) { + newNumNewMessages = (Integer) evt.getNewValue(); + } + + if (newNumNewMessages != numNewMessages) { + ingestMessagesButton.setMessages(newNumNewMessages); + numNewMessages = newNumNewMessages; + ingestMessagesButton.repaint(); + } } }); From f0cb03b717cad31eb25e3622cbb3ef190484ad83 Mon Sep 17 00:00:00 2001 From: Tim McIver Date: Mon, 25 Mar 2013 16:51:18 -0400 Subject: [PATCH 25/25] Fixed issue that was giving the wrong color to rows in the ingest message box. Changed height and width of inges message box window. --- .../autopsy/core/floatingLeftBottomWsmode.xml | 2 +- .../autopsy/ingest/IngestMessagePanel.java | 63 +++++++------------ 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/floatingLeftBottomWsmode.xml b/Core/src/org/sleuthkit/autopsy/core/floatingLeftBottomWsmode.xml index dcc47dc5cc..d6c6eca099 100644 --- a/Core/src/org/sleuthkit/autopsy/core/floatingLeftBottomWsmode.xml +++ b/Core/src/org/sleuthkit/autopsy/core/floatingLeftBottomWsmode.xml @@ -12,7 +12,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index e8122c5903..b183d9633b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java @@ -823,20 +823,15 @@ class IngestMessagePanel extends JPanel implements TableModelListener { // increase the font size cell.setFont(new Font("", Font.PLAIN, 16)); - if (isSelected) { - final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); - MessageType mt = messageGroup.getMessageType(); - if (mt == MessageType.ERROR) { - cell.setBackground(ERROR_COLOR); - } else if (mt == MessageType.WARNING) { - cell.setBackground(Color.orange); - } else { - //cell.setBackground(table.getBackground()); - cell.setBackground(messageGroup.getColor()); - } + final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); + MessageType mt = messageGroup.getMessageType(); + if (mt == MessageType.ERROR) { + cell.setBackground(ERROR_COLOR); + } else if (mt == MessageType.WARNING) { + cell.setBackground(Color.orange); } else { - super.setForeground(table.getSelectionForeground()); - super.setBackground(table.getSelectionBackground()); + //cell.setBackground(table.getBackground()); + cell.setBackground(messageGroup.getColor()); } return cell; @@ -869,20 +864,15 @@ class IngestMessagePanel extends JPanel implements TableModelListener { } } - if (isSelected) { - final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); - MessageType mt = messageGroup.getMessageType(); - if (mt == MessageType.ERROR) { - cell.setBackground(ERROR_COLOR); - } else if (mt == MessageType.WARNING) { - cell.setBackground(Color.orange); - } else { - //cell.setBackground(table.getBackground()); - cell.setBackground(messageGroup.getColor()); - } + final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); + MessageType mt = messageGroup.getMessageType(); + if (mt == MessageType.ERROR) { + cell.setBackground(ERROR_COLOR); + } else if (mt == MessageType.WARNING) { + cell.setBackground(Color.orange); } else { - super.setForeground(table.getSelectionForeground()); - super.setBackground(table.getSelectionBackground()); + //cell.setBackground(table.getBackground()); + cell.setBackground(messageGroup.getColor()); } return cell; @@ -908,20 +898,15 @@ class IngestMessagePanel extends JPanel implements TableModelListener { Component cell = super.getTableCellRendererComponent(table, aValue, isSelected, hasFocus, row, column); - if (isSelected) { - final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); - MessageType mt = messageGroup.getMessageType(); - if (mt == MessageType.ERROR) { - cell.setBackground(ERROR_COLOR); - } else if (mt == MessageType.WARNING) { - cell.setBackground(Color.orange); - } else { - //cell.setBackground(table.getBackground()); - cell.setBackground(messageGroup.getColor()); - } + final IngestMessageGroup messageGroup = tableModel.getMessageGroup(row); + MessageType mt = messageGroup.getMessageType(); + if (mt == MessageType.ERROR) { + cell.setBackground(ERROR_COLOR); + } else if (mt == MessageType.WARNING) { + cell.setBackground(Color.orange); } else { - super.setForeground(table.getSelectionForeground()); - super.setBackground(table.getSelectionBackground()); + //cell.setBackground(table.getBackground()); + cell.setBackground(messageGroup.getColor()); } return cell;