diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form index 14f2a4f112..07fb3e514e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form @@ -130,6 +130,9 @@ + + + @@ -143,6 +146,9 @@ + + + @@ -168,6 +174,9 @@ + + + @@ -178,6 +187,9 @@ + + + @@ -192,6 +204,9 @@ + + + @@ -199,6 +214,9 @@ + + + @@ -213,6 +231,9 @@ + + + @@ -257,6 +278,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 9fb5b41089..dfc96a244a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -18,9 +18,14 @@ */ package org.sleuthkit.autopsy.corecomponents; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.text.NumberFormat; import javax.swing.DefaultComboBoxModel; import javax.swing.JFormattedTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.core.UserPreferences; @@ -29,7 +34,9 @@ import org.sleuthkit.autopsy.core.UserPreferences; */ final class AutopsyOptionsPanel extends javax.swing.JPanel { - AutopsyOptionsPanel(AutopsyOptionsPanelController controller) { + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + + AutopsyOptionsPanel() { initComponents(); int availableProcessors = Runtime.getRuntime().availableProcessors(); Integer fileIngestThreadCountChoices[]; @@ -71,6 +78,25 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { numberOfFileIngestThreadsComboBox.setModel(new DefaultComboBoxModel<>(fileIngestThreadCountChoices)); restartRequiredLabel.setText(NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.restartRequiredLabel.text", recommendedFileIngestThreadCount)); // TODO listen to changes in form fields and call controller.changed() + DocumentListener docListener = new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + + @Override + public void removeUpdate(DocumentEvent e) { + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + + @Override + public void changedUpdate(DocumentEvent e) { + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + }; + this.jFormattedTextFieldProcTimeOutHrs.getDocument().addDocumentListener(docListener); + } void load() { @@ -113,6 +139,16 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { } } + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + boolean valid() { return true; } @@ -148,10 +184,20 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { useBestViewerRB.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(useBestViewerRB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useBestViewerRB.text")); // NOI18N useBestViewerRB.setToolTipText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useBestViewerRB.toolTipText")); // NOI18N + useBestViewerRB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + useBestViewerRBActionPerformed(evt); + } + }); buttonGroup1.add(keepCurrentViewerRB); org.openide.awt.Mnemonics.setLocalizedText(keepCurrentViewerRB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.keepCurrentViewerRB.text")); // NOI18N keepCurrentViewerRB.setToolTipText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.keepCurrentViewerRB.toolTipText")); // NOI18N + keepCurrentViewerRB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + keepCurrentViewerRBActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectFile, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelSelectFile.text")); // NOI18N @@ -160,18 +206,44 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { buttonGroup3.add(useLocalTimeRB); useLocalTimeRB.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(useLocalTimeRB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useLocalTimeRB.text")); // NOI18N + useLocalTimeRB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + useLocalTimeRBActionPerformed(evt); + } + }); buttonGroup3.add(useGMTTimeRB); org.openide.awt.Mnemonics.setLocalizedText(useGMTTimeRB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useGMTTimeRB.text")); // NOI18N + useGMTTimeRB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + useGMTTimeRBActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(jLabelHideKnownFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideKnownFiles.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideKnownCB.text")); // NOI18N + dataSourcesHideKnownCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dataSourcesHideKnownCBActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(viewsHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideKnownCB.text")); // NOI18N + viewsHideKnownCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + viewsHideKnownCBActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(jLabelNumThreads, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelNumThreads.text")); // NOI18N + numberOfFileIngestThreadsComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + numberOfFileIngestThreadsComboBoxActionPerformed(evt); + } + }); + restartRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(restartRequiredLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.restartRequiredLabel.text")); // NOI18N @@ -187,6 +259,11 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(jLabelProcessTimeOutUnits, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelProcessTimeOutUnits.text")); // NOI18N jFormattedTextFieldProcTimeOutHrs.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jFormattedTextFieldProcTimeOutHrs.text")); // NOI18N + jFormattedTextFieldProcTimeOutHrs.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jFormattedTextFieldProcTimeOutHrsActionPerformed(evt); + } + }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -271,8 +348,41 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private void jCheckBoxEnableProcTimeoutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBoxEnableProcTimeoutActionPerformed jFormattedTextFieldProcTimeOutHrs.setEditable(jCheckBoxEnableProcTimeout.isSelected()); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_jCheckBoxEnableProcTimeoutActionPerformed + private void useBestViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRBActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_useBestViewerRBActionPerformed + + private void keepCurrentViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keepCurrentViewerRBActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_keepCurrentViewerRBActionPerformed + + private void dataSourcesHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideKnownCBActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_dataSourcesHideKnownCBActionPerformed + + private void viewsHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideKnownCBActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_viewsHideKnownCBActionPerformed + + private void useLocalTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useLocalTimeRBActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_useLocalTimeRBActionPerformed + + private void useGMTTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useGMTTimeRBActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_useGMTTimeRBActionPerformed + + private void numberOfFileIngestThreadsComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_numberOfFileIngestThreadsComboBoxActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_numberOfFileIngestThreadsComboBoxActionPerformed + + private void jFormattedTextFieldProcTimeOutHrsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jFormattedTextFieldProcTimeOutHrsActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_jFormattedTextFieldProcTimeOutHrsActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; private javax.swing.ButtonGroup buttonGroup3; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java index 9253da5df5..8caca949bc 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java @@ -18,15 +18,13 @@ */ package org.sleuthkit.autopsy.corecomponents; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import javax.swing.JComponent; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.HelpCtx; import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; @OptionsPanelController.TopLevelRegistration(categoryName = "#OptionsCategory_Name_General", @@ -41,18 +39,33 @@ public final class AutopsyOptionsPanelController extends OptionsPanelController private boolean changed; private static final Logger logger = Logger.getLogger(AutopsyOptionsPanelController.class.getName()); + /** + * Component should load its data here. + */ @Override public void update() { getPanel().load(); changed = false; } + /** + * This method is called when both the Ok and Apply buttons are pressed. It + * applies to any of the panels that have been opened in the process of + * using the options pane. + */ @Override public void applyChanges() { - getPanel().store(); - changed = false; + if (changed) { + getPanel().store(); + changed = false; + } } + /** + * This method is called when the Cancel button is pressed. It applies to + * any of the panels that have been opened in the process of using the + * options pane. + */ @Override public void cancel() { } @@ -62,6 +75,12 @@ public final class AutopsyOptionsPanelController extends OptionsPanelController return getPanel().valid(); } + /** + * Used to determine whether any changes have been made to this controller's + * panel. + * + * @return Whether or not a change has been made. + */ @Override public boolean isChanged() { return changed; @@ -89,7 +108,15 @@ public final class AutopsyOptionsPanelController extends OptionsPanelController private AutopsyOptionsPanel getPanel() { if (panel == null) { - panel = new AutopsyOptionsPanel(this); + panel = new AutopsyOptionsPanel(); + panel.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) { + changed(); + } + } + }); } return panel; } @@ -97,26 +124,9 @@ public final class AutopsyOptionsPanelController extends OptionsPanelController void changed() { if (!changed) { changed = true; - - try { - pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); - } catch (Exception e) { - logger.log(Level.SEVERE, "GeneralOptionsPanelController listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr"), - NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr.msg"), - MessageNotifyUtil.MessageType.ERROR); - } + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); - try { - pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); - } catch (Exception e) { - logger.log(Level.SEVERE, "GeneralOptionsPanelController listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr"), - NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr.msg"), - MessageNotifyUtil.MessageType.ERROR); - } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java index e3715787cf..12e41d9e13 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java @@ -42,7 +42,6 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.corecomponents.DataContentTopComponent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskCoreException; @@ -111,7 +110,7 @@ public final class Reports implements AutopsyVisitableItem { @Override public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); - if (eventType.equals(Case.Events.REPORT_ADDED.toString())) { + if (eventType.equals(Case.Events.REPORT_ADDED.toString()) || eventType.equals(Case.Events.REPORT_DELETED.toString())) { /** * Checking for a current case is a stop gap measure * until a different way of handling the closing of @@ -272,7 +271,6 @@ public final class Reports implements AutopsyVisitableItem { JOptionPane.YES_NO_OPTION) == 0) { try { Case.getCurrentCase().deleteReports(selectedReportsCollection, checkbox.isSelected()); - DataContentTopComponent.findInstance().repaint(); } catch (TskCoreException | IllegalStateException ex) { Logger.getLogger(DeleteReportAction.class.getName()).log(Level.INFO, "Error deleting the reports. ", ex); // NON-NLS - Provide solution to the user? } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index d60c9507f3..d352956556 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -18,11 +18,9 @@ */ package org.sleuthkit.autopsy.directorytree; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.actions.AddContentTagAction; -import java.awt.Toolkit; import java.awt.Dimension; import java.awt.Font; +import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; @@ -37,6 +35,8 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.table.DefaultTableModel; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; @@ -112,6 +112,9 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final VirtualDirectory d) { List actions = new ArrayList<>(); + if (!d.isDataSource()) { + actions.add(AddContentTagAction.getInstance()); + } actions.add(ExtractAction.getInstance()); actions.addAll(ContextMenuExtensionPoint.getActions()); return actions; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties index 680f029304..59f23b4239 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties @@ -114,4 +114,3 @@ IngestJobSettingsPanel.globalSettingsButton.text=Global Settings gest IngestJobSettingsPanel.globalSettingsButton.actionCommand=Advanced IngestJobSettingsPanel.globalSettingsButton.text=Global Settings -IngestJobSettingsPanel.descriptionLabel.text=dummmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy texxxxxxxxxxxxxxxttttttttttttttttttttttttttttttttttttttttttt diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index 2c6bef517c..12930abba9 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -1032,7 +1032,7 @@ final class DataSourceIngestJob { */ private void logIngestModuleErrors(List errors) { for (IngestModuleError error : errors) { - DataSourceIngestJob.logger.log(Level.SEVERE, String.format("%s experienced an error analyzing %s (jobId=%d)", error.getModuleDisplayName(), dataSource.getName(), this.id), error.getModuleError()); //NON-NLS + DataSourceIngestJob.logger.log(Level.SEVERE, String.format("%s experienced an error analyzing %s (jobId=%d)", error.getModuleDisplayName(), dataSource.getName(), this.id), error.getThrowable()); //NON-NLS } } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.form index 7298579aed..cb6eb5bf9d 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.form @@ -181,9 +181,7 @@ - - - + diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java index f947c28359..fb7d165d5c 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java @@ -184,7 +184,7 @@ public final class IngestJobSettingsPanel extends javax.swing.JPanel { } }); - descriptionLabel.setText(org.openide.util.NbBundle.getMessage(IngestJobSettingsPanel.class, "IngestJobSettingsPanel.descriptionLabel.text")); // NOI18N + descriptionLabel.setText("DO NOT REMOVE. This dummy text is used to anchor the inner panel's size to the outer panel, while still being expandable. Without this the expandability behavior doesn't work well. This text will never be shown, as it would only be shown when no module is selected (which is not possible)."); jScrollPane1.setBorder(null); jScrollPane1.setPreferredSize(new java.awt.Dimension(250, 180)); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 017840ae9b..840c4cd732 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -65,12 +65,6 @@ import org.sleuthkit.datamodel.Content; * Manages the creation and execution of ingest jobs, i.e., the processing of * data sources by ingest modules. */ -@NbBundle.Messages({ - "# {0} - error message", "IngestManager.StartIngestJobsTask.run.startupErr.dlgErrorList=\nErrors: \n{0}", - "IngestManager.StartIngestJobsTask.run.startupErr.dlgSolution=Please disable the failed modules or fix the errors and then restart ingest \nby right clicking on the data source and selecting Run Ingest Modules.", - "IngestManager.StartIngestJobsTask.run.startupErr.dlgMsg=Unable to start up one or more ingest modules, ingest job cancelled.", - "IngestManager.StartIngestJobsTask.run.startupErr.dlgTitle=Ingest Failure" -}) public class IngestManager { private static final Logger logger = Logger.getLogger(IngestManager.class.getName()); @@ -532,6 +526,12 @@ public class IngestManager { * @return The IngestJobStartResult describing the results of attempting to * start the ingest job. */ + @NbBundle.Messages({ + "IngestManager.startupErr.dlgTitle=Ingest Module Startup Failure", + "IngestManager.startupErr.dlgMsg=Unable to start up one or more ingest modules, ingest cancelled.", + "IngestManager.startupErr.dlgSolution=Please disable the failed modules or fix the errors before restarting ingest.", + "IngestManager.startupErr.dlgErrorList=Errors:" + }) private IngestJobStartResult startIngestJob(IngestJob job) { List errors = null; if (this.jobCreationIsEnabled) { @@ -576,47 +576,29 @@ public class IngestManager { this.jobsById.remove(job.getId()); } for (IngestModuleError error : errors) { - logger.log(Level.SEVERE, String.format("Error starting %s ingest module for job %d", error.getModuleDisplayName(), job.getId()), error.getModuleError()); //NON-NLS + logger.log(Level.SEVERE, String.format("Error starting %s ingest module for job %d", error.getModuleDisplayName(), job.getId()), error.getThrowable()); //NON-NLS } IngestManager.logger.log(Level.SEVERE, "Ingest job {0} could not be started", job.getId()); //NON-NLS if (RuntimeProperties.coreComponentsAreActive()) { - - StringBuilder moduleStartUpErrors = new StringBuilder(); + final StringBuilder message = new StringBuilder(); + message.append(Bundle.IngestManager_startupErr_dlgMsg()).append("\n"); + message.append(Bundle.IngestManager_startupErr_dlgSolution()).append("\n\n"); + message.append(Bundle.IngestManager_startupErr_dlgErrorList()).append("\n"); for (IngestModuleError error : errors) { String moduleName = error.getModuleDisplayName(); - moduleStartUpErrors.append(moduleName); - moduleStartUpErrors.append(": "); - moduleStartUpErrors.append(error.getModuleError().getLocalizedMessage()); - moduleStartUpErrors.append("\n"); + String errorMessage = error.getThrowable().getLocalizedMessage(); + message.append(moduleName).append(": ").append(errorMessage).append("\n"); } - EventQueue.invokeLater(new NotifyUserOfErrors(moduleStartUpErrors.toString())); + message.append("\n\n"); + EventQueue.invokeLater(() -> { + JOptionPane.showMessageDialog(null, message, Bundle.IngestManager_startupErr_dlgTitle(), JOptionPane.ERROR_MESSAGE); + }); } } } return new IngestJobStartResult(job, null, errors); } - class NotifyUserOfErrors implements Runnable { - - private String startupErrors; - - NotifyUserOfErrors(String startupErrors) { - this.startupErrors = startupErrors; - } - - @Override - public void run() { - StringBuilder notifyMessage = new StringBuilder(); - notifyMessage.append(Bundle.IngestManager_StartIngestJobsTask_run_startupErr_dlgMsg()); - notifyMessage.append("\n"); - notifyMessage.append(Bundle.IngestManager_StartIngestJobsTask_run_startupErr_dlgSolution()); - notifyMessage.append("\n"); - notifyMessage.append(Bundle.IngestManager_StartIngestJobsTask_run_startupErr_dlgErrorList(startupErrors)); - notifyMessage.append("\n\n"); - JOptionPane.showMessageDialog(null, notifyMessage.toString(), Bundle.IngestManager_StartIngestJobsTask_run_startupErr_dlgTitle(), JOptionPane.ERROR_MESSAGE); - } - } - synchronized void finishIngestJob(IngestJob job) { long jobId = job.getId(); synchronized (jobsById) { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleError.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleError.java index 22ea0f9c08..1ce3770360 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleError.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleError.java @@ -19,24 +19,54 @@ package org.sleuthkit.autopsy.ingest; /** - * Encapsulates an exception thrown by an ingest module during an operation such - * as startup or shut down with an exception object for the error that occurred. + * Encapsulates a Throwable thrown by an ingest module with the display name of + * the module for logging purposes. */ public final class IngestModuleError { private final String moduleDisplayName; - private final Throwable error; + private final Throwable throwable; - IngestModuleError(String moduleDisplayName, Throwable error) { + /** + * Constructs an object that encapsulates a Throwable thrown by an ingest + * module with the display name of the module for logging purposes. + * + * @param moduleDisplayName The display name of the module. + * @param throwable The throwable. + */ + IngestModuleError(String moduleDisplayName, Throwable throwable) { this.moduleDisplayName = moduleDisplayName; - this.error = error; + this.throwable = throwable; } + /** + * Gets the module display name. + * + * @return The module display name. + */ public String getModuleDisplayName() { return this.moduleDisplayName; } + /** + * Gets the throwable. + * + * @return The Throwable + */ + public Throwable getThrowable() { + return this.throwable; + } + + /** + * Gets the throwable. + * + * @return The Throwable + * + * @deprecated Use getThrowable instead. + * + */ + @Deprecated public Throwable getModuleError() { - return this.error; + return this.throwable; } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/AndroidIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/android/AndroidIngestModule.java index a7856a4b6a..9be5ab8055 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/AndroidIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/AndroidIngestModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,28 +19,19 @@ package org.sleuthkit.autopsy.modules.android; import java.util.ArrayList; -import java.util.HashMap; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.FileManager; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestModule; import org.sleuthkit.datamodel.Content; import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; -import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; -import org.sleuthkit.autopsy.ingest.IngestServices; class AndroidIngestModule implements DataSourceIngestModule { - private static final HashMap fileCountsForIngestJobs = new HashMap<>(); private IngestJobContext context = null; - private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); - private static final Logger logger = Logger.getLogger(AndroidIngestModule.class.getName()); - private IngestServices services = IngestServices.getInstance(); @Override public void startUp(IngestJobContext context) throws IngestModuleException { @@ -49,9 +40,6 @@ class AndroidIngestModule implements DataSourceIngestModule { @Override public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { - services.postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO, AndroidModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), "AndroidIngestModule.processing.startedAnalysis"))); - ArrayList errors = new ArrayList<>(); progressBar.switchToDeterminate(9); FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); @@ -155,11 +143,6 @@ class AndroidIngestModule implements DataSourceIngestModule { errorMsgSubject = "No errors"; //NON-NLS } - services.postMessage(IngestMessage.createMessage(msgLevel, AndroidModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), - "AndroidIngestModule.processing.finishedAnalysis", - errorMsgSubject), errorMessage.toString())); - return IngestModule.ProcessResult.OK; } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/android/Bundle.properties index a771b8d7ab..9ef8ab1338 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/android/Bundle.properties @@ -1,7 +1,5 @@ AndroidModuleFactory.moduleName=Android Analyzer AndroidModuleFactory.moduleDescription=Extracts Android system and third-party app data. -AndroidIngestModule.processing.startedAnalysis=Started Analysis -AndroidIngestModule.processing.finishedAnalysis=Finished Analysis\: {0} BrowserLocationAnalyzer.bbAttribute.browserLocationHistory=Browser Location History CacheLocationAnalyzer.bbAttribute.fileLocationHistory={0} Location History GoogleMapLocationAnalyzer.bbAttribute.destination=Destination diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/android/Bundle_ja.properties index 0e009d26dd..e555ddc54c 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/android/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/android/Bundle_ja.properties @@ -1,13 +1,10 @@ -AndroidModuleFactory.moduleDescription=Android\u30B7\u30B9\u30C6\u30E0\u304A\u3088\u3073\u7B2C\u4E09\u8005\u30A2\u30D7\u30EA\u30C7\u30FC\u30BF\u3092\u62BD\u51FA -AndroidIngestModule.processing.finishedAnalysis=\u5B8C\u4E86\u3057\u305F\u89E3\u6790\uFF1A{0} -AndroidIngestModule.processing.startedAnalysis=\u958B\u59CB\u3057\u305F\u89E3\u6790 -AndroidModuleFactory.moduleName=Android\u30A2\u30CA\u30E9\u30A4\u30B6 -BrowserLocationAnalyzer.bbAttribute.browserLocationHistory=\u30D6\u30E9\u30A6\u30B6\u30ED\u30B1\u30FC\u30B7\u30E7\u30F3\u5C65\u6B74 -CacheLocationAnalyzer.bbAttribute.fileLocationHistory={0} \u30ED\u30B1\u30FC\u30B7\u30E7\u30F3\u5C65\u6B74 -GoogleMapLocationAnalyzer.bbAttribute.destination=\u76EE\u7684\u5730 -GoogleMapLocationAnalyzer.bbAttribute.googleMapsHistory=Google\u30DE\u30C3\u30D7\u5C65\u6B74 -TangoMessageAnalyzer.bbAttribute.tangoMessage=Tango\u30E1\u30C3\u30BB\u30FC\u30B8 -TextMessageAnalyzer.bbAttribute.incoming=\u53D7\u4FE1 -TextMessageAnalyzer.bbAttribute.outgoing=\u9001\u4FE1 -TextMessageAnalyzer.bbAttribute.smsMessage=SMS\u30E1\u30C3\u30BB\u30FC\u30B8 -WWFMessageAnalyzer.bbAttribute.wordsWithFriendsMsg=Words With Friends\u30E1\u30C3\u30BB\u30FC\u30B8 \ No newline at end of file +AndroidModuleFactory.moduleDescription=Android\u30b7\u30b9\u30c6\u30e0\u304a\u3088\u3073\u7b2c\u4e09\u8005\u30a2\u30d7\u30ea\u30c7\u30fc\u30bf\u3092\u62bd\u51fa +BrowserLocationAnalyzer.bbAttribute.browserLocationHistory=\u30d6\u30e9\u30a6\u30b6\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u5c65\u6b74 +CacheLocationAnalyzer.bbAttribute.fileLocationHistory={0} \u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u5c65\u6b74 +GoogleMapLocationAnalyzer.bbAttribute.destination=\u76ee\u7684\u5730 +GoogleMapLocationAnalyzer.bbAttribute.googleMapsHistory=Google\u30de\u30c3\u30d7\u5c65\u6b74 +TangoMessageAnalyzer.bbAttribute.tangoMessage=Tango\u30e1\u30c3\u30bb\u30fc\u30b8 +TextMessageAnalyzer.bbAttribute.incoming=\u53d7\u4fe1 +TextMessageAnalyzer.bbAttribute.outgoing=\u9001\u4fe1 +TextMessageAnalyzer.bbAttribute.smsMessage=SMS\u30e1\u30c3\u30bb\u30fc\u30b8 +WWFMessageAnalyzer.bbAttribute.wordsWithFriendsMsg=Words With Friends\u30e1\u30c3\u30bb\u30fc\u30b8 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java index cb601c62a7..dd7ae145fe 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java @@ -150,7 +150,7 @@ class ImageExtractor { } } } catch (TskCoreException e) { - logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", parentFileName); //NON-NLS + logger.log(Level.WARNING, String.format("Error checking if file already has been processed, skipping: %s", parentFileName), e); //NON-NLS return; } switch (abstractFileExtractionFormat) { @@ -209,21 +209,21 @@ class ImageExtractor { HWPFDocument doc = null; try { doc = new HWPFDocument(new ReadContentInputStream(af)); - } catch (Throwable ignore) { + } catch (Throwable ex) { // instantiating POI containers throw RuntimeExceptions - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docContainer.init.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docContainer.init.err", af.getName()), ex); //NON-NLS return null; } - + PicturesTable pictureTable = null; List listOfAllPictures = null; try { pictureTable = doc.getPicturesTable(); - listOfAllPictures = pictureTable.getAllPictures(); - } catch (Exception ignore) { + listOfAllPictures = pictureTable.getAllPictures(); + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS - return null; + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS + return null; } String outputFolderPath; @@ -241,9 +241,9 @@ class ImageExtractor { String fileName = picture.suggestFullFileName(); try { data = picture.getContent(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS return null; } writeExtractedImage(Paths.get(outputFolderPath, fileName).toString(), data); @@ -267,18 +267,18 @@ class ImageExtractor { XWPFDocument docx = null; try { docx = new XWPFDocument(new ReadContentInputStream(af)); - } catch (Throwable ignore) { + } catch (Throwable ex) { // instantiating POI containers throw RuntimeExceptions - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docxContainer.init.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docxContainer.init.err", af.getName()), ex); //NON-NLS return null; } List listOfAllPictures = null; try { listOfAllPictures = docx.getAllPictures(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS - return null; + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS + return null; } // if no images are extracted from the PPT, return null, else initialize @@ -299,9 +299,9 @@ class ImageExtractor { String fileName = xwpfPicture.getFileName(); try { data = xwpfPicture.getData(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS return null; } writeExtractedImage(Paths.get(outputFolderPath, fileName).toString(), data); @@ -323,9 +323,9 @@ class ImageExtractor { SlideShow ppt = null; try { ppt = new SlideShow(new ReadContentInputStream(af)); - } catch (Throwable ignore) { + } catch (Throwable ex) { // instantiating POI containers throw RuntimeExceptions - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptContainer.init.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptContainer.init.err", af.getName()), ex); //NON-NLS return null; } @@ -333,10 +333,10 @@ class ImageExtractor { PictureData[] listOfAllPictures = null; try { listOfAllPictures = ppt.getPictureData(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS - return null; + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS + return null; } // if no images are extracted from the PPT, return null, else initialize @@ -385,9 +385,9 @@ class ImageExtractor { String imageName = UNKNOWN_NAME_PREFIX + i + ext; //NON-NLS try { data = pictureData.getData(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS return null; } writeExtractedImage(Paths.get(outputFolderPath, imageName).toString(), data); @@ -410,18 +410,18 @@ class ImageExtractor { XMLSlideShow pptx; try { pptx = new XMLSlideShow(new ReadContentInputStream(af)); - } catch (Throwable ignore) { + } catch (Throwable ex) { // instantiating POI containers throw RuntimeExceptions - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptxContainer.init.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptxContainer.init.err", af.getName()), ex); //NON-NLS return null; } List listOfAllPictures = null; try { listOfAllPictures = pptx.getAllPictures(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS - return null; + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS + return null; } // if no images are extracted from the PPT, return null, else initialize @@ -446,9 +446,9 @@ class ImageExtractor { String fileName = xslsPicture.getFileName(); try { data = xslsPicture.getData(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS return null; } writeExtractedImage(Paths.get(outputFolderPath, fileName).toString(), data); @@ -474,21 +474,21 @@ class ImageExtractor { Workbook xls; try { xls = new HSSFWorkbook(new ReadContentInputStream(af)); - } catch (Throwable ignore) { + } catch (Throwable ex) { // instantiating POI containers throw RuntimeExceptions - logger.log(Level.WARNING, "{0}{1}", new Object[]{NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err", af.getName()), af.getName()}); //NON-NLS + logger.log(Level.WARNING, String.format("%s%s", NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err", af.getName()), af.getName()), ex); //NON-NLS return null; } List listOfAllPictures = null; try { listOfAllPictures = xls.getAllPictures(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS - return null; + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS + return null; } - + // if no images are extracted from the PPT, return null, else initialize // the output folder for image extraction. String outputFolderPath; @@ -509,9 +509,9 @@ class ImageExtractor { String imageName = UNKNOWN_NAME_PREFIX + i + "." + pictureData.suggestFileExtension(); //NON-NLS try { data = pictureData.getData(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS return null; } writeExtractedImage(Paths.get(outputFolderPath, imageName).toString(), data); @@ -535,21 +535,21 @@ class ImageExtractor { Workbook xlsx; try { xlsx = new XSSFWorkbook(new ReadContentInputStream(af)); - } catch (Throwable ignore) { + } catch (Throwable ex) { // instantiating POI containers throw RuntimeExceptions - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err", af.getName()), ex); //NON-NLS return null; } List listOfAllPictures = null; try { listOfAllPictures = xlsx.getAllPictures(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS - return null; + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS + return null; } - + // if no images are extracted from the PPT, return null, else initialize // the output folder for image extraction. String outputFolderPath; @@ -570,9 +570,9 @@ class ImageExtractor { String imageName = UNKNOWN_NAME_PREFIX + i + "." + pictureData.suggestFileExtension(); try { data = pictureData.getData(); - } catch (Exception ignore) { + } catch (Exception ex) { // log internal Java and Apache errors as WARNING - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName())); //NON-NLS + logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.processing.err", af.getName()), ex); //NON-NLS return null; } writeExtractedImage(Paths.get(outputFolderPath, imageName).toString(), data); diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchOptionsPanelController.java index 8a4638ac28..013c8f65a4 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchOptionsPanelController.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchOptionsPanelController.java @@ -4,15 +4,13 @@ */ package org.sleuthkit.autopsy.modules.fileextmismatch; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import javax.swing.JComponent; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.HelpCtx; import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; @OptionsPanelController.TopLevelRegistration( @@ -28,19 +26,33 @@ public final class FileExtMismatchOptionsPanelController extends OptionsPanelCon private boolean changed; private static final Logger logger = Logger.getLogger(FileExtMismatchOptionsPanelController.class.getName()); + /** + * Component should load its data here. + */ @Override public void update() { getPanel().load(); changed = false; } + /** + * This method is called when both the Ok and Apply buttons are pressed. It + * applies to any of the panels that have been opened in the process of + * using the options pane. + */ @Override public void applyChanges() { - //getPanel().store(); - getPanel().ok(); - changed = false; + if (changed) { + getPanel().ok(); + changed = false; + } } + /** + * This method is called when the Cancel button is pressed. It applies to + * any of the panels that have been opened in the process of using the + * options pane. + */ @Override public void cancel() { getPanel().cancel(); @@ -51,6 +63,12 @@ public final class FileExtMismatchOptionsPanelController extends OptionsPanelCon return getPanel().valid(); } + /** + * Used to determine whether any changes have been made to this controller's + * panel. + * + * @return Whether or not a change has been made. + */ @Override public boolean isChanged() { return changed; @@ -79,6 +97,14 @@ public final class FileExtMismatchOptionsPanelController extends OptionsPanelCon private FileExtMismatchSettingsPanel getPanel() { if (panel == null) { panel = new FileExtMismatchSettingsPanel(); + panel.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) { + changed(); + } + } + }); } return panel; } @@ -87,25 +113,9 @@ public final class FileExtMismatchOptionsPanelController extends OptionsPanelCon if (!changed) { changed = true; - try { - pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); - } catch (Exception e) { - logger.log(Level.SEVERE, "FileExtMismatchOptionsPanelController listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "FileExtMismatchOptionsPanelController.moduleErr"), - NbBundle.getMessage(this.getClass(), "FileExtMismatchOptionsPanelController.moduleErr.msg"), - MessageNotifyUtil.MessageType.ERROR); - } + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); } - try { - pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); - } catch (Exception e) { - logger.log(Level.SEVERE, "FileExtMismatchOptionsPanelController listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "FileExtMismatchOptionsPanelController.moduleErr"), - NbBundle.getMessage(this.getClass(), "FileExtMismatchOptionsPanelController.moduleErr.msg"), - MessageNotifyUtil.MessageType.ERROR); - } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java index 6361adf3c7..f253a41bc1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.modules.fileextmismatch; import java.awt.Color; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -30,6 +32,7 @@ import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; +import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; @@ -54,6 +57,7 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel private String selectedExt = ""; ListSelectionModel lsm = null; private FileTypeDetector fileTypeDetector; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); public FileExtMismatchSettingsPanel() { mimeTableModel = new MimeTableModel(); @@ -132,6 +136,16 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel addExtButton.setEnabled(false); } + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + private void clearErrLabels() { mimeErrLabel.setText(" "); extErrorLabel.setText(" "); @@ -378,6 +392,7 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel updateExtList(); extTableModel.resync(); this.userExtTextField.setText(""); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_addExtButtonActionPerformed private void addTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addTypeButtonActionPerformed @@ -424,6 +439,7 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel updateMimeList(); mimeTableModel.resync(); userTypeTextField.setText(""); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_addTypeButtonActionPerformed private void userExtTextFieldFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_userExtTextFieldFocusGained @@ -448,6 +464,7 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel // Refresh table updateMimeList(); mimeTableModel.resync(); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_removeTypeButtonActionPerformed private void removeExtButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeExtButtonActionPerformed @@ -475,6 +492,7 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel // Refresh tables updateExtList(); extTableModel.resync(); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_removeExtButtonActionPerformed private void updateMimeList() { diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.java index b8fdbb92cf..a002cc12ed 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.java @@ -25,6 +25,8 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JDialog; @@ -36,7 +38,7 @@ import org.openide.util.NbBundle.Messages; /** * Dialog used for editing or adding file types. */ -public class AddFileTypeDialog extends JDialog { +class AddFileTypeDialog extends JDialog { /** * Enum used for letting creator of this dialog know whether or not OK was @@ -51,6 +53,8 @@ public class AddFileTypeDialog extends JDialog { private FileType fileType; private AddFileTypePanel addMimeTypePanel; private BUTTON_PRESSED result; + private JButton okButton; + private JButton closeButton; /** * Creates a dialog for creating a file type @@ -100,13 +104,12 @@ public class AddFileTypeDialog extends JDialog { add(this.addMimeTypePanel, BorderLayout.PAGE_START); // Add the add/done button. - JButton addButton; if (add) { - addButton = new JButton(Bundle.AddMimeTypeDialog_addButton_title()); + okButton = new JButton(Bundle.AddMimeTypeDialog_addButton_title()); } else { - addButton = new JButton(Bundle.AddMimeTypeDialog_addButton_title2()); + okButton = new JButton(Bundle.AddMimeTypeDialog_addButton_title2()); } - addButton.addActionListener(new ActionListener() { + okButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doButtonAction(true); @@ -114,7 +117,7 @@ public class AddFileTypeDialog extends JDialog { }); // Add a close button. - JButton closeButton = new JButton(Bundle.AddMimeTypeDialog_cancelButton_title()); + closeButton = new JButton(Bundle.AddMimeTypeDialog_cancelButton_title()); closeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -126,7 +129,7 @@ public class AddFileTypeDialog extends JDialog { JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); buttonPanel.add(new javax.swing.Box.Filler(new Dimension(10, 10), new Dimension(10, 10), new Dimension(10, 10))); - buttonPanel.add(addButton); + buttonPanel.add(okButton); buttonPanel.add(new javax.swing.Box.Filler(new Dimension(10, 10), new Dimension(10, 10), new Dimension(10, 10))); buttonPanel.add(closeButton); add(buttonPanel, BorderLayout.LINE_START); @@ -141,13 +144,22 @@ public class AddFileTypeDialog extends JDialog { doButtonAction(false); } }); - + this.addMimeTypePanel.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(AddFileTypePanel.EVENT.SIG_LIST_CHANGED.toString())) { + enableOkButton(); + } + } + }); + enableOkButton(); /** * Show the dialog. */ pack(); setResizable(false); setVisible(true); + } /** @@ -189,4 +201,8 @@ public class AddFileTypeDialog extends JDialog { return result; } + private void enableOkButton() { + this.okButton.setEnabled(addMimeTypePanel.hasSignature()); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java index 72a92824f1..bc44e8884f 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.modules.filetypeid; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.List; import javax.swing.DefaultListModel; @@ -26,18 +28,20 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; +import static org.sleuthkit.autopsy.modules.filetypeid.AddFileTypePanel.EVENT.SIG_LIST_CHANGED; import org.sleuthkit.autopsy.modules.filetypeid.AddFileTypeSignatureDialog.BUTTON_PRESSED; import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; /** * Panel for adding or editing file types. */ -public class AddFileTypePanel extends javax.swing.JPanel { +class AddFileTypePanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private AddFileTypeSignatureDialog addSigDialog; private DefaultListModel signaturesListModel; + private PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** * Creates a panel for a new file type. @@ -49,6 +53,10 @@ public class AddFileTypePanel extends javax.swing.JPanel { this.addTypeListSelectionListener(); this.enableButtons(); } + + enum EVENT { + SIG_LIST_CHANGED + } /** * Creates a panel for editing a file type. @@ -136,6 +144,20 @@ public class AddFileTypePanel extends javax.swing.JPanel { deleteSigButton.setEnabled(true); } } + + boolean hasSignature() { + return !this.signaturesListModel.isEmpty(); + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } /** * This method is called from within the constructor to initialize the form. @@ -257,6 +279,7 @@ public class AddFileTypePanel extends javax.swing.JPanel { if (!this.signaturesListModel.isEmpty()) { signatureList.setSelectedIndex(0); } + pcs.firePropertyChange(SIG_LIST_CHANGED.toString(), null, null); } }//GEN-LAST:event_deleteSigButtonActionPerformed @@ -266,6 +289,7 @@ public class AddFileTypePanel extends javax.swing.JPanel { if (addSigDialog.getResult() == AddFileTypeSignatureDialog.BUTTON_PRESSED.OK) { signaturesListModel.addElement(this.addSigDialog.getSignature()); } + pcs.firePropertyChange(SIG_LIST_CHANGED.toString(), null, null); } }//GEN-LAST:event_addSigButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java index 43dfc31fa0..3db4cc5d68 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.modules.filetypeid; import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.util.Collections; import java.util.List; import javax.swing.DefaultComboBoxModel; @@ -28,6 +29,7 @@ import javax.swing.DefaultListModel; import javax.swing.JOptionPane; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.ingest.IngestManager; @@ -67,6 +69,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane // modules obtained and shared a per data source ingest job snapshot of the // file type definitions. IngestJobEventPropertyChangeListener ingestJobEventsListener; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** * Creates a panel to allow a user to make custom file type definitions. @@ -92,6 +95,16 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane populateTypeDetailsComponents(); } + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + /** * Sets the list model for the list of file types. */ @@ -451,6 +464,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane if (result == AddFileTypeDialog.BUTTON_PRESSED.OK) { fileTypes.add(dialog.getFileType()); updateFileTypesListModel(); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } }//GEN-LAST:event_newTypeButtonActionPerformed @@ -462,6 +476,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane if (!typesListModel.isEmpty()) { typesList.setSelectedIndex(0); } + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_deleteTypeButtonActionPerformed private void editTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editTypeButtonActionPerformed @@ -472,6 +487,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane this.fileTypes.remove(selected); this.fileTypes.add(selected, dialog.getFileType()); updateFileTypesListModel(); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } }//GEN-LAST:event_editTypeButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdOptionsPanelController.java index d4b7270f45..cea2f62de1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdOptionsPanelController.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdOptionsPanelController.java @@ -5,6 +5,7 @@ */ package org.sleuthkit.autopsy.modules.filetypeid; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import javax.swing.JComponent; @@ -19,26 +20,41 @@ import org.openide.util.Lookup; keywordsCategory = "FileTypeId", position = 6 ) -// moved messages to Bundle.properties -//@org.openide.util.NbBundle.Messages({"OptionsCategory_Name_FileTypeId=FileTypeId", "OptionsCategory_Keywords_FileTypeId=FileTypeId"}) public final class FileTypeIdOptionsPanelController extends OptionsPanelController { private FileTypeIdGlobalSettingsPanel panel; private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private boolean changed; + /** + * Component should load its data here. + */ @Override public void update() { + getPanel().load(); changed = false; + } + /** + * This method is called when both the Ok and Apply buttons are pressed. It + * applies to any of the panels that have been opened in the process of + * using the options pane. + */ @Override public void applyChanges() { - getPanel().store(); - changed = false; + if (changed) { + getPanel().store(); + changed = false; + } } + /** + * This method is called when the Cancel button is pressed. It applies to + * any of the panels that have been opened in the process of using the + * options pane. + */ @Override public void cancel() { } @@ -48,6 +64,12 @@ public final class FileTypeIdOptionsPanelController extends OptionsPanelControll return true; } + /** + * Used to determine whether any changes have been made to this controller's + * panel. + * + * @return Whether or not a change has been made. + */ @Override public boolean isChanged() { return changed; @@ -76,6 +98,14 @@ public final class FileTypeIdOptionsPanelController extends OptionsPanelControll private FileTypeIdGlobalSettingsPanel getPanel() { if (panel == null) { panel = new FileTypeIdGlobalSettingsPanel(); + panel.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) { + changed(); + } + } + }); } return panel; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDatabaseOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDatabaseOptionsPanelController.java index def594863a..ac90c2e29f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDatabaseOptionsPanelController.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDatabaseOptionsPanelController.java @@ -24,9 +24,6 @@ import javax.swing.JComponent; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.HelpCtx; import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; @OptionsPanelController.TopLevelRegistration( @@ -45,18 +42,33 @@ public final class HashDatabaseOptionsPanelController extends OptionsPanelContro private boolean changed; private static final Logger logger = Logger.getLogger(HashDatabaseOptionsPanelController.class.getName()); + /** + * Component should load its data here. + */ @Override public void update() { getPanel().load(); changed = false; } + /** + * This method is called when both the Ok and Apply buttons are pressed. It + * applies to any of the panels that have been opened in the process of + * using the options pane. + */ @Override public void applyChanges() { - getPanel().store(); - changed = false; + if (changed) { + getPanel().store(); + changed = false; + } } + /** + * This method is called when the Cancel button is pressed. It applies to + * any of the panels that have been opened in the process of using the + * options pane. + */ @Override public void cancel() { getPanel().cancel(); @@ -67,6 +79,12 @@ public final class HashDatabaseOptionsPanelController extends OptionsPanelContro return getPanel().valid(); } + /** + * Used to determine whether any changes have been made to this controller's + * panel. + * + * @return Whether or not a change has been made. + */ @Override public boolean isChanged() { return changed; @@ -103,25 +121,9 @@ public final class HashDatabaseOptionsPanelController extends OptionsPanelContro if (!changed) { changed = true; - try { - pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); - } catch (Exception e) { - logger.log(Level.SEVERE, "HashDatabaseOptionsPanelController listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "HashDatabaseOptionsPanelController.moduleErr"), - NbBundle.getMessage(this.getClass(), "HashDatabaseOptionsPanelController.moduleErrMsg"), - MessageNotifyUtil.MessageType.ERROR); - } + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); } - try { - pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); - } catch (Exception e) { - logger.log(Level.SEVERE, "HashDatabaseOptionsPanelController listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "HashDatabaseOptionsPanelController.moduleErr"), - NbBundle.getMessage(this.getClass(), "HashDatabaseOptionsPanelController.moduleErrMsg"), - MessageNotifyUtil.MessageType.ERROR); - } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index 022604928e..22bb501f22 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -729,7 +729,7 @@ public class HashDbManager implements PropertyChangeListener { return SleuthkitJNI.hashDatabaseHasLookupIndex(handle); } - boolean hasIndexOnly() throws TskCoreException { + public boolean hasIndexOnly() throws TskCoreException { return SleuthkitJNI.hashDatabaseIsIndexOnly(handle); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsOptionsPanelController.java index 127d0c45af..61f4e8f9fc 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsOptionsPanelController.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsOptionsPanelController.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.modules.interestingitems; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import javax.swing.JComponent; @@ -39,23 +40,38 @@ public final class InterestingItemDefsOptionsPanelController extends OptionsPane private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private boolean changed; + /** + * Component should load its data here. + */ @Override public void update() { getPanel().load(); changed = false; } + /** + * This method is called when both the Ok and Apply buttons are pressed. It + * applies to any of the panels that have been opened in the process of + * using the options pane. + */ @Override public void applyChanges() { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - getPanel().store(); - changed = false; - } - }); + if (changed) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + getPanel().store(); + changed = false; + } + }); + } } + /** + * This method is called when the Cancel button is pressed. It applies to + * any of the panels that have been opened in the process of using the + * options pane. + */ @Override public void cancel() { // need not do anything special, if no changes have been persisted yet @@ -66,6 +82,12 @@ public final class InterestingItemDefsOptionsPanelController extends OptionsPane return true; } + /** + * Used to determine whether any changes have been made to this controller's + * panel. + * + * @return Whether or not a change has been made. + */ @Override public boolean isChanged() { return changed; @@ -94,6 +116,14 @@ public final class InterestingItemDefsOptionsPanelController extends OptionsPane private InterestingItemDefsPanel getPanel() { if (panel == null) { panel = new InterestingItemDefsPanel(); + panel.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) { + changed(); + } + } + }); } return panel; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java index 61271fc54c..dba9091bb7 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java @@ -19,7 +19,8 @@ package org.sleuthkit.autopsy.modules.interestingitems; import java.awt.EventQueue; -import java.awt.Font; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -35,15 +36,14 @@ import javax.swing.JOptionPane; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.apache.tika.mime.MediaType; +import org.apache.tika.mime.MimeTypes; +import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; -import org.apache.tika.mime.MediaType; -import org.apache.tika.mime.MimeTypes; -import org.openide.util.Exceptions; -import org.sleuthkit.autopsy.coreutils.Logger; /** * A panel that allows a user to make interesting item definitions. @@ -74,6 +74,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp // definitions manager. Note that it is a tree map to aid in displaying // files sets in sorted order by name. private TreeMap filesSets; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** * Constructs an interesting item definitions panel. @@ -121,6 +122,16 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp this.fileSizeUnitComboBox.setSelectedIndex(1); this.equalitySignComboBox.setSelectedIndex(2); } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } /** * @inheritDoc @@ -907,6 +918,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp private void newSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newSetButtonActionPerformed this.doFileSetsDialog(null); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_newSetButtonActionPerformed private void deleteRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteRuleButtonActionPerformed @@ -919,6 +931,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp FilesSet.Rule selectedRule = this.rulesList.getSelectedValue(); rules.remove(selectedRule.getUuid()); this.replaceFilesSet(oldSet, oldSet.getName(), oldSet.getDescription(), oldSet.ignoresKnownFiles(), rules); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_deleteRuleButtonActionPerformed private void deleteSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteSetButtonActionPerformed @@ -933,6 +946,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp } else { this.resetComponents(); } + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_deleteSetButtonActionPerformed private void ignoreKnownFilesCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ignoreKnownFilesCheckboxActionPerformed @@ -941,14 +955,17 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp private void editSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editSetButtonActionPerformed this.doFileSetsDialog(this.setsList.getSelectedValue()); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_editSetButtonActionPerformed private void editRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleButtonActionPerformed this.doFilesSetRuleDialog(this.rulesList.getSelectedValue()); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_editRuleButtonActionPerformed private void newRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newRuleButtonActionPerformed this.doFilesSetRuleDialog(null); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_newRuleButtonActionPerformed private void fileNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileNameTextFieldActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form index 119000eebe..3df3f78f59 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form @@ -87,9 +87,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java index f57da241f9..a140357a50 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java @@ -22,7 +22,6 @@ import java.awt.Component; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; -import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -36,12 +35,13 @@ import javax.swing.JList; import javax.swing.JPanel; import javax.swing.ListCellRenderer; import javax.swing.ListModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.ListDataListener; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -68,6 +68,26 @@ final class ReportVisualPanel2 extends JPanel { deselectAllButton.setEnabled(false); allResultsRadioButton.setSelected(true); this.wizPanel = wizPanel; + this.allResultsRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + tagsList.setEnabled(taggedResultsRadioButton.isSelected()); + selectAllButton.setEnabled(taggedResultsRadioButton.isSelected()); + deselectAllButton.setEnabled(taggedResultsRadioButton.isSelected()); + advancedButton.setEnabled(!taggedResultsRadioButton.isSelected()); + updateFinishButton(); + } + }); + this.taggedResultsRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + tagsList.setEnabled(taggedResultsRadioButton.isSelected()); + selectAllButton.setEnabled(taggedResultsRadioButton.isSelected()); + deselectAllButton.setEnabled(taggedResultsRadioButton.isSelected()); + advancedButton.setEnabled(!taggedResultsRadioButton.isSelected()); + updateFinishButton(); + } + }); } // Initialize the list of Tags @@ -164,21 +184,11 @@ final class ReportVisualPanel2 extends JPanel { return result; } - private boolean areArtifactsSelected() { - boolean result = false; - for (Entry entry : artifactStates.entrySet()) { - if (entry.getValue()) { - result = true; - } - } - return result; - } - private void updateFinishButton() { if (taggedResultsRadioButton.isSelected()) { wizPanel.setFinish(areTagsSelected()); } else { - wizPanel.setFinish(areArtifactsSelected()); + wizPanel.setFinish(true); } } @@ -211,11 +221,6 @@ final class ReportVisualPanel2 extends JPanel { optionsButtonGroup.add(taggedResultsRadioButton); org.openide.awt.Mnemonics.setLocalizedText(taggedResultsRadioButton, org.openide.util.NbBundle.getMessage(ReportVisualPanel2.class, "ReportVisualPanel2.taggedResultsRadioButton.text")); // NOI18N - taggedResultsRadioButton.addChangeListener(new javax.swing.event.ChangeListener() { - public void stateChanged(javax.swing.event.ChangeEvent evt) { - taggedResultsRadioButtonStateChanged(evt); - } - }); optionsButtonGroup.add(allResultsRadioButton); org.openide.awt.Mnemonics.setLocalizedText(allResultsRadioButton, org.openide.util.NbBundle.getMessage(ReportVisualPanel2.class, "ReportVisualPanel2.allResultsRadioButton.text")); // NOI18N @@ -293,14 +298,6 @@ final class ReportVisualPanel2 extends JPanel { ); }// //GEN-END:initComponents - private void taggedResultsRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_taggedResultsRadioButtonStateChanged - tagsList.setEnabled(taggedResultsRadioButton.isSelected()); - selectAllButton.setEnabled(taggedResultsRadioButton.isSelected()); - deselectAllButton.setEnabled(taggedResultsRadioButton.isSelected()); - advancedButton.setEnabled(!taggedResultsRadioButton.isSelected()); - updateFinishButton(); - }//GEN-LAST:event_taggedResultsRadioButtonStateChanged - private void selectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectAllButtonActionPerformed for (String tag : tags) { tagStates.put(tag, Boolean.TRUE); @@ -319,8 +316,8 @@ final class ReportVisualPanel2 extends JPanel { private void advancedButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_advancedButtonActionPerformed artifactStates = dialog.display(); - wizPanel.setFinish(areArtifactsSelected()); }//GEN-LAST:event_advancedButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton advancedButton; private javax.swing.JRadioButton allResultsRadioButton; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index ec80b5180d..e50e1909a8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-15 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,29 +19,24 @@ package org.sleuthkit.autopsy.timeline.actions; import java.awt.Desktop; -import java.io.File; -import java.io.FileWriter; +import java.awt.image.BufferedImage; import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.LocalDateTime; -import java.util.function.Consumer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.function.Supplier; import java.util.logging.Level; import javafx.embed.swing.SwingFXUtils; -import javafx.event.ActionEvent; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; +import javafx.scene.control.TextInputDialog; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.stage.Modality; -import javafx.stage.StageStyle; -import javax.imageio.ImageIO; import javax.swing.JOptionPane; +import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.HyperlinkLabel; import org.controlsfx.control.action.Action; import org.openide.util.NbBundle; @@ -49,131 +44,115 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; +import org.sleuthkit.autopsy.timeline.snapshot.SnapShotReportWriter; import org.sleuthkit.datamodel.TskCoreException; /** - * Save a snapshot of the given node as an autopsy report. + * Action that saves a snapshot of the given node as an autopsy report. + * Delegates to SnapsHotReportWrite to actually generate and write the report. */ public class SaveSnapshotAsReport extends Action { private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName()); - private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16, true, true); // NON-NLS - private static final String HTML_EXT = ".html"; // NON-NLS - private static final String REPORT_IMAGE_EXTENSION = ".png"; // NON-NLS - private static final ButtonType open = new ButtonType(Bundle.OpenReportAction_DisplayName(), ButtonBar.ButtonData.NO); - private static final ButtonType ok = new ButtonType(ButtonType.OK.getText(), ButtonBar.ButtonData.CANCEL_CLOSE); + private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16, true, true); //NON_NLS + private static final ButtonType OPEN = new ButtonType(Bundle.OpenReportAction_DisplayName(), ButtonBar.ButtonData.NO); + private static final ButtonType OK = new ButtonType(ButtonType.OK.getText(), ButtonBar.ButtonData.CANCEL_CLOSE); - @NbBundle.Messages({"SaveSnapshot.action.name.text=Snapshot Report", - "SaveSnapshot.action.longText=Save a screen capture of the visualization as a report.", - "SaveSnapshot.fileChoose.title.text=Save snapshot to", + private final TimeLineController controller; + private final Case currentCase; + + /** + * Constructor + * + * @param controller The controller for this timeline action + * @param nodeSupplier The Supplier of the node to snapshot. + */ + @NbBundle.Messages({ + "Timeline.ModuleName=Timeline", + "SaveSnapShotAsReport.action.dialogs.title=Timeline", + "SaveSnapShotAsReport.action.name.text=Snapshot Report", + "SaveSnapShotAsReport.action.longText=Save a screen capture of the visualization as a report.", "# {0} - report file path", "SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]", - "Timeline.ModuleName=Timeline", "SaveSnapShotAsReport.Success=Success", - "# {0} - uniqueness identifier, local date time at report creation time", - "SaveSnapsHotAsReport.ReportName=timeline-report-{0}", - "SaveSnapShotAsReport.FailedToAddReport=Failed to add snaphot as a report. See log for details", - "# {0} - report name", - "SaveSnapShotAsReport.ErrorWritingReport=Error writing report {0} to disk. See log for details",}) - public SaveSnapshotAsReport(TimeLineController controller, Node node) { - super(Bundle.SaveSnapshot_action_name_text()); - setLongText(Bundle.SaveSnapshot_action_longText()); + "SaveSnapShotAsReport.Success=Success", + "SaveSnapShotAsReport.FailedToAddReport=Failed to add snaphot to case as a report.", + "# {0} - report path", + "SaveSnapShotAsReport.ErrorWritingReport=Error writing report to disk at {0}.", + "# {0} - generated default report name", + "SaveSnapShotAsReport.reportName.prompt=leave empty for default report name: {0}.", + "SaveSnapShotAsReport.reportName.header=Enter a report name for the Timeline Snapshot Report." + }) + public SaveSnapshotAsReport(TimeLineController controller, Supplier nodeSupplier) { + super(Bundle.SaveSnapShotAsReport_action_name_text()); + setLongText(Bundle.SaveSnapShotAsReport_action_longText()); setGraphic(new ImageView(SNAP_SHOT)); - setEventHandler(new Consumer() { - @Override - public void accept(ActionEvent actioneEvent) { - String escapedLocalDateTime = FileUtil.escapeFileName(LocalDateTime.now().toString()); - String reportName = Bundle.SaveSnapsHotAsReport_ReportName(escapedLocalDateTime); - Path reportPath = Paths.get(Case.getCurrentCase().getReportDirectory(), reportName).toAbsolutePath(); - File reportHTMLFIle = reportPath.resolve(reportName + HTML_EXT).toFile(); + this.controller = controller; + this.currentCase = controller.getAutopsyCase(); - ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get(); + setEventHandler(actionEvent -> { + //capture generation date and use to make default report name + Date generationDate = new Date(); + final String defaultReportName = FileUtil.escapeFileName(currentCase.getName() + " " + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss").format(generationDate)); //NON_NLS + BufferedImage snapshot = SwingFXUtils.fromFXImage(nodeSupplier.get().snapshot(null, null), null); + + //prompt user to pick report name + TextInputDialog textInputDialog = new TextInputDialog(); + textInputDialog.setTitle(Bundle.SaveSnapShotAsReport_action_dialogs_title()); + textInputDialog.getEditor().setPromptText(Bundle.SaveSnapShotAsReport_reportName_prompt(defaultReportName)); + //keep prompt even if text field has focus, until user starts typing. + textInputDialog.getEditor().setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");//NON_NLS + textInputDialog.setHeaderText(Bundle.SaveSnapShotAsReport_reportName_header()); + + textInputDialog.showAndWait().ifPresent(enteredReportName -> { + //reportName defaults to case name + timestamp if left blank + String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName); + Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, "Timeline Snapshot"); //NON_NLS + Path reportMainFilePath; try { - //ensure directory exists and write html file - Files.createDirectories(reportPath); - try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) { - writeHTMLFile(reportName, htmlWriter, zoomParams); - } - - //take snapshot and save in report directory - ImageIO.write(SwingFXUtils.fromFXImage(node.snapshot(null, null), null), "png", // NON-NLS - reportPath.resolve(reportName + REPORT_IMAGE_EXTENSION).toFile()); // NON-NLS - - //copy report css - try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS - Files.copy(resource, reportPath.resolve("index.css")); // NON-NLS - } - - //add html file as report to case - try { - Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), Bundle.Timeline_ModuleName(), reportName + HTML_EXT); // NON-NLS - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "failed to add html wrapper as a report", ex); // NON-NLS - new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_FailedToAddReport()).showAndWait(); - } - - //create alert to notify user of report location - final Alert alert = new Alert(Alert.AlertType.INFORMATION, null, open, ok); - alert.setTitle(Bundle.SaveSnapshot_action_name_text()); - alert.setHeaderText(Bundle.SaveSnapShotAsReport_Success()); - alert.initStyle(StageStyle.UTILITY); - alert.initOwner(node.getScene().getWindow()); - alert.initModality(Modality.APPLICATION_MODAL); - - //make action to open report, and hyperlinklable to invoke action - final OpenReportAction openReportAction = new OpenReportAction(reportHTMLFIle); - HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(Bundle.SaveSnapShotAsReport_ReportSavedAt(reportHTMLFIle.getPath())); - hyperlinkLabel.setOnAction(openReportAction); - alert.getDialogPane().setContent(hyperlinkLabel); - - alert.showAndWait().ifPresent(buttonType -> { - if (buttonType == open) { - openReportAction.handle(null); - } - }); - - } catch (IOException e) { - LOGGER.log(Level.SEVERE, "Error writing report " + reportPath + " to disk", e); // NON-NLS - new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_ErrorWritingReport(reportPath)).showAndWait(); + //generate and write report + reportMainFilePath = new SnapShotReportWriter(currentCase, + reportFolderPath, + reportName, + controller.getEventsModel().getZoomParamaters(), + generationDate, snapshot).writeReport(); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Error writing report to disk at " + reportFolderPath, ex); //NON_NLS + new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_ErrorWritingReport(reportFolderPath)).show(); + return; } - } + + try { + //add main file as report to case + Case.getCurrentCase().addReport(reportMainFilePath.toString(), Bundle.Timeline_ModuleName(), reportName); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Failed to add " + reportMainFilePath.toString() + " to case as a report", ex); //NON_NLS + new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_FailedToAddReport()).show(); + return; + } + + //notify user of report location + final Alert alert = new Alert(Alert.AlertType.INFORMATION, null, OPEN, OK); + alert.setTitle(Bundle.SaveSnapShotAsReport_action_dialogs_title()); + alert.setHeaderText(Bundle.SaveSnapShotAsReport_Success()); + + //make action to open report, and hyperlinklable to invoke action + final OpenReportAction openReportAction = new OpenReportAction(reportMainFilePath); + HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(Bundle.SaveSnapShotAsReport_ReportSavedAt(reportMainFilePath.toString())); + hyperlinkLabel.setOnAction(openReportAction); + alert.getDialogPane().setContent(hyperlinkLabel); + + alert.showAndWait().filter(OPEN::equals).ifPresent(buttonType -> openReportAction.handle(null)); + }); }); } - private static void writeHTMLFile(String reportName, final Writer htmlWriter, ZoomParams zoomParams) throws IOException { - - //write html wrapper file - htmlWriter.write("\n\n\ttimeline snapshot\n\t\n\n\n"); // NON-NLS - htmlWriter.write("
\n

" + reportName + "

\n"); // NON-NLS - //embed snapshot - htmlWriter.write("\"snaphot\""); // NON-NLS - //write view paramaters - htmlWriter.write("\n"); // NON-NLS - writeTableRow(htmlWriter, "Case", Case.getCurrentCase().getName()); // NON-NLS - writeTableRow(htmlWriter, "Time Range", zoomParams.getTimeRange().toString()); // NON-NLS - writeTableRow(htmlWriter, "Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName()); // NON-NLS - writeTableRow(htmlWriter, "Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName()); // NON-NLS - writeTableRow(htmlWriter, "Filters", zoomParams.getFilter().getHTMLReportString()); // NON-NLS - //end table and html - htmlWriter.write("
\n"); // NON-NLS - htmlWriter.write("
\n\n"); // NON-NLS - } - /** - * - * @param htmlWriter the value of htmlWriter - * @param key the value of Key - * @param value the value of value - * - * @throws IOException + * Action that opens the given Path in the system default application. */ - private static void writeTableRow(final Writer htmlWriter, final String key, final String value) throws IOException { - htmlWriter.write("" + key + ": " + value + "\n"); // NON-NLS - } - - @NbBundle.Messages({"OpenReportAction.DisplayName=Open Report", + @NbBundle.Messages({ + "OpenReportAction.DisplayName=Open Report", "OpenReportAction.NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.", "OpenReportAction.MessageBoxTitle=Open Report Failure", "OpenReportAction.NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.", @@ -181,11 +160,11 @@ public class SaveSnapshotAsReport extends Action { "OpenReportAction.ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied."}) private class OpenReportAction extends Action { - OpenReportAction(File reportHTMLFIle) { + OpenReportAction(Path reportHTMLFIle) { super(Bundle.OpenReportAction_DisplayName()); setEventHandler(actionEvent -> { try { - Desktop.getDesktop().open(reportHTMLFIle); + Desktop.getDesktop().open(reportHTMLFIle.toFile()); } catch (IOException ex) { JOptionPane.showMessageDialog(null, Bundle.OpenReportAction_NoAssociatedEditorMessage(), diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index b9d4f85447..ad5bc8786e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -162,10 +162,24 @@ public final class FilteredEventsModel { requestedZoomParamters.bind(currentStateProperty); } + /** + * Readonly observable property for the current ZoomParams + * + * @return A readonly observable property for the current ZoomParams. + */ synchronized public ReadOnlyObjectProperty zoomParametersProperty() { return requestedZoomParamters.getReadOnlyProperty(); } + /** + * Get the current ZoomParams + * + * @return The current ZoomParams + */ + synchronized public ZoomParams getZoomParamaters() { + return requestedZoomParamters.get(); + } + /** * Get a read only view of the time range currently in view. * diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java index 14394546ba..8a1999ed5d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java @@ -63,10 +63,7 @@ public abstract class AbstractFilter implements Filter { return disabledProperty().get(); } - @Override - public String getStringCheckBox() { - return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS - } + @Override public boolean isActive() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java index c713965933..f7229ca34c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java @@ -54,11 +54,6 @@ public class DataSourceFilter extends AbstractFilter { return getDataSourceName(); } - @Override - public String getHTMLReportString() { - return getDisplayName() + getStringCheckBox(); - } - @Override public int hashCode() { int hash = 5; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java index 3395982ad7..45d5ce2427 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.function.Predicate; -import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.value.ObservableBooleanValue; @@ -57,19 +56,6 @@ public class DataSourcesFilter extends UnionFilter { return Bundle.DataSourcesFilter_displayName_text(); } - @Override - public String getHTMLReportString() { - //move this logic into SaveSnapshot - String string = getDisplayName() + getStringCheckBox(); - if (getSubFilters().isEmpty() == false) { - string = string + " : " + getSubFilters().stream() - .filter(Filter::isSelected) - .map(Filter::getHTMLReportString) - .collect(Collectors.joining("
  • ", "
    • ", "
    ")); // NON-NLS - } - return string; - } - @Override public boolean equals(Object obj) { if (obj == null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index c31049e21d..60f72c4e7c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.Objects; - import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; @@ -52,11 +51,6 @@ public class DescriptionFilter extends AbstractFilter { return getDescriptionLoD().getDisplayName() + ": " + getDescription(); } - @Override - public String getHTMLReportString() { - return getDisplayName() + getStringCheckBox(); - } - /** * @return the descriptionLoD */ @@ -72,7 +66,7 @@ public class DescriptionFilter extends AbstractFilter { } @NbBundle.Messages({"DescriptionFilter.mode.exclude=Exclude", - "DescriptionFilter.mode.include=Include"}) + "DescriptionFilter.mode.include=Include"}) public enum FilterMode { EXCLUDE(Bundle.DescriptionFilter_mode_exclude()), diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java index 92e04dcf81..76d1915342 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java @@ -73,21 +73,6 @@ public interface Filter { */ String getDisplayName(); - /** - * get a representation of this filter (and it's state) as a HTML string - * - * @return a html representation of this filter - */ - String getHTMLReportString(); - - /** - * get an Ascii representation of this filter's selected state: ie [x] for - * selected or [ ] for not selected - * - * @return an Ascii representation of this filter's selected state - */ - String getStringCheckBox(); - /** * is this filter selected * diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java index ba2791ac1e..bb5b94d533 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.function.Predicate; -import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableBooleanValue; import org.openide.util.NbBundle; @@ -51,19 +50,7 @@ public class HashHitsFilter extends UnionFilter { return filterCopy; } - @Override - public String getHTMLReportString() { - //move this logic into SaveSnapshot - String string = getDisplayName() + getStringCheckBox(); - if (getSubFilters().isEmpty() == false) { - string = string + " : " + getSubFilters().stream() - .filter(Filter::isSelected) - .map(Filter::getHTMLReportString) - .collect(Collectors.joining("
  • ", "
    • ", "
    ")); // NON-NLS - } - return string; - } - + @Override public int hashCode() { return 7; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java index f2d5b82a36..f0ee7625d9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java @@ -54,11 +54,6 @@ public class HashSetFilter extends AbstractFilter { return hashSetName; } - @Override - public String getHTMLReportString() { - return getDisplayName() + getStringCheckBox(); - } - @Override public int hashCode() { int hash = 7; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java index f5e5b30731..89f311ff3c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java @@ -44,11 +44,6 @@ public class HideKnownFilter extends AbstractFilter { return hideKnownFilter; } - @Override - public String getHTMLReportString() { - return "hide known" + getStringCheckBox();// NON-NLS - } - @Override public int hashCode() { return 7; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java index 7295a066b2..7021881dda 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java @@ -58,14 +58,6 @@ public class IntersectionFilter extends CompoundFilter { return Bundle.IntersectionFilter_displayName_text(collect); } - @Override - public String getHTMLReportString() { - return getSubFilters().stream() - .filter(Filter::isSelected) - .map(Filter::getHTMLReportString) - .collect(Collectors.joining("
  • ", "
    • ", "
    ")); // NON-NLS - } - @Override public boolean equals(Object obj) { if (obj == null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index 8b26ed1c71..4adffce8d6 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -24,8 +24,8 @@ import javafx.beans.binding.BooleanBinding; import javafx.collections.FXCollections; /** - * an implementation of (@link IntersectionFilter} designed to be used as the - * root of a filter tree. provides named access to specific subfilters. + * An implementation of IntersectionFilter designed to be used as the root of a + * filter tree. provides named access to specific subfilters. */ public class RootFilter extends IntersectionFilter { @@ -48,6 +48,18 @@ public class RootFilter extends IntersectionFilter { return hashFilter; } + public TypeFilter getTypeFilter() { + return typeFilter; + } + + public HideKnownFilter getKnownFilter() { + return knownFilter; + } + + public TextFilter getTextFilter() { + return textFilter; + } + public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, Set annonymousSubFilters) { super(FXCollections.observableArrayList( textFilter, @@ -110,11 +122,14 @@ public class RootFilter extends IntersectionFilter { return areSubFiltersEqual(this, (CompoundFilter) obj); } + @Override public boolean isActive() { return true; } + @Override public BooleanBinding activeProperty() { + return new BooleanBinding() { @Override protected boolean computeValue() { @@ -122,8 +137,4 @@ public class RootFilter extends IntersectionFilter { } }; } - - public TypeFilter getTypeFilter() { - return typeFilter; - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java index 284d39274c..e80df2de3c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java @@ -59,11 +59,6 @@ public class TagNameFilter extends AbstractFilter { return tagName.getDisplayName(); } - @Override - public String getHTMLReportString() { - return getDisplayName() + getStringCheckBox(); - } - @Override public int hashCode() { int hash = 3; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java index 7d23c49916..7285003023 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.Comparator; import java.util.function.Predicate; -import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableBooleanValue; import org.openide.util.NbBundle; @@ -53,19 +52,6 @@ public class TagsFilter extends UnionFilter { return filterCopy; } - @Override - public String getHTMLReportString() { - //move this logic into SaveSnapshot - String string = getDisplayName() + getStringCheckBox(); - if (getSubFilters().isEmpty() == false) { - string = string + " : " + getSubFilters().stream() - .filter(Filter::isSelected) - .map(Filter::getHTMLReportString) - .collect(Collectors.joining("
  • ", "
    • ", "
    ")); // NON-NLS - } - return string; - } - @Override public int hashCode() { return 7; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TextFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TextFilter.java index e907b1bd05..0ea4ab4994 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TextFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TextFilter.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.Objects; import javafx.beans.property.Property; import javafx.beans.property.SimpleStringProperty; -import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; /** @@ -64,11 +63,6 @@ public class TextFilter extends AbstractFilter { return textFilter; } - @Override - public String getHTMLReportString() { - return "LOWER(text) LIKE LOWER(\'" + StringUtils.defaultIfBlank(text.getValue(), "") + "\')" + getStringCheckBox(); // NON-NLS - } - @Override public boolean equals(Object obj) { if (obj == null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java index b897791985..8d23ef4c57 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.Comparator; import java.util.Objects; import java.util.function.Predicate; -import java.util.stream.Collectors; import javafx.collections.FXCollections; import javafx.scene.image.Image; import javafx.scene.paint.Color; @@ -109,15 +108,6 @@ public class TypeFilter extends UnionFilter { return filterCopy; } - @Override - public String getHTMLReportString() { - String string = getEventType().getDisplayName() + getStringCheckBox(); - if (getSubFilters().isEmpty() == false) { - string = string + " : " + getSubFilters().stream().filter(Filter::isSelected).map(Filter::getHTMLReportString).collect(Collectors.joining("
  • ", "
    • ", "
    ")); // NON-NLS - } - return string; - } - @Override public boolean equals(Object obj) { if (obj == null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/question-frame.png b/Core/src/org/sleuthkit/autopsy/timeline/images/question-frame.png new file mode 100644 index 0000000000..be52814717 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/question-frame.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/index.css b/Core/src/org/sleuthkit/autopsy/timeline/index.css deleted file mode 100644 index fd6e48afd9..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/index.css +++ /dev/null @@ -1,16 +0,0 @@ -body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;} -#content {padding: 30px;} -#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;} -h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;} -h2 {font-size: 20px; font-weight: bolder; color: #07A;} -h3 {font-size: 16px; color: #07A;} -h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;} -ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;} -ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;} -ul li a:hover {text-decoration: underline;} -p {margin: 0 0 20px 0;} -table {max-width: 100%; min-width: 700px; padding: 0; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;} -.keyword_list table {width: 100%; margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;} -table th {display: table-cell; text-align: left; padding: 8px 16px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;} -table td {display: table-cell; padding: 8px 16px; font: 13px/20px Arial, Helvetica, sans-serif; max-width: 500px; min-width: 125px; word-break: break-all; overflow: auto;} -table tr:nth-child(even) td {background: #f3f3f3;} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java new file mode 100644 index 0000000000..75377ff5bb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java @@ -0,0 +1,229 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.timeline.snapshot; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import javax.imageio.ImageIO; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.format.DateTimeFormat; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.report.ReportBranding; +import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; + +/** + * Generate and write the Timeline snapshot report to disk. + */ +public class SnapShotReportWriter { + + /** + * mustache.java template factory. + */ + private final static MustacheFactory mf = new DefaultMustacheFactory(); + + private final Case currentCase; + private final Path reportFolderPath; + private final String reportName; + private final ReportBranding reportBranding; + + private final ZoomParams zoomParams; + private final Date generationDate; + private final BufferedImage image; + + /** + * Constructor + * + * @param currentCase The Case to write a report for. + * @param reportFolderPath The Path to the folder that will contain the + * report. + * @param reportName The name of the report. + * @param zoomParams The ZoomParams in effect when the snapshot was + * taken. + * @param generationDate The generation Date of the report. + * @param snapshot A snapshot of the visualization to include in the + * report. + */ + public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) { + this.currentCase = currentCase; + this.reportFolderPath = reportFolderPath; + this.reportName = reportName; + this.zoomParams = zoomParams; + this.generationDate = generationDate; + this.image = snapshot; + + this.reportBranding = new ReportBranding(); + } + + /** + * Generate and write the report to disk. + * + * @return The Path to the "main file" of the report. This is the file that + * Autopsy shows in the results view when the Reports Node is + * selected in the DirectoryTree. + * + * @throws IOException If there is a problem writing the report. + */ + public Path writeReport() throws IOException { + //ensure directory exists + Files.createDirectories(reportFolderPath); + + //save the snapshot in the report directory + ImageIO.write(image, "png", reportFolderPath.resolve("snapshot.png").toFile()); //NON-NLS + + copyResources(); + + writeSummaryHTML(); + writeSnapShotHTMLFile(); + return writeIndexHTML(); + } + + /** + * Generate and write the html page that shows the snapshot and the state of + * the ZoomParams + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private void writeSnapShotHTMLFile() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap snapShotContext = new HashMap<>(); + snapShotContext.put("reportTitle", reportName); //NON-NLS + snapShotContext.put("startTime", zoomParams.getTimeRange().getStart().toString(DateTimeFormat.fullDateTime())); //NON-NLS + snapShotContext.put("endTime", zoomParams.getTimeRange().getEnd().toString(DateTimeFormat.fullDateTime())); //NON-NLS + snapShotContext.put("zoomParams", zoomParams); //NON-NLS + + fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, reportFolderPath.resolve("snapshot.html")); //NON-NLS + } + + /** + * Generate and write the main html page with frames for navigation on the + * left and content on the right. + * + * @return The Path of the written html file. + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private Path writeIndexHTML() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap indexContext = new HashMap<>(); + indexContext.put("currentCase", currentCase); //NON-NLS + Path reportIndexFile = reportFolderPath.resolve("index.html"); //NON-NLS + + fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/index_template.html", "Index", indexContext, reportIndexFile); //NON-NLS + return reportIndexFile; + } + + /** + * * Generate and write the summary of the current case for this report. + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private void writeSummaryHTML() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap summaryContext = new HashMap<>(); + summaryContext.put("reportName", reportName); //NON-NLS + summaryContext.put("reportBranding", reportBranding); //NON-NLS + summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); //NON-NLS + summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); //NON-NLS + summaryContext.put("currentCase", currentCase); //NON-NLS + + fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); //NON-NLS + } + + /** + * Fill in the mustache template at the given location using the values from + * the given context object and save it to the given outPutFile. + * + * @param templateLocation The location of the template. suitible for use + * with Class.getResourceAsStream + * @param templateName The name of the tempalte. (Used by mustache to + * cache templates?) + * @param context The contect to use to fill in the template + * values. + * @param outPutFile The filled in tempalte will be saced at this + * Path. + * + * @throws IOException If there is a problem saving the filled in template + * to disk. + */ + private void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException { + + Mustache summaryMustache = mf.compile(new InputStreamReader(SnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName); + try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { //NON-NLS + summaryMustache.execute(writer, context); + } + } + + /** + * Copy static resources (static html, css, images, etc) to the reports + * folder. + * + * @throws IOException If there is a problem copying the resources. + */ + private void copyResources() throws IOException { + + //pull generator and agency logos from branding + String generatorLogoPath = reportBranding.getGeneratorLogoPath(); + if (StringUtils.isNotBlank(generatorLogoPath)) { + Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); //NON-NLS + } + String agencyLogoPath = reportBranding.getAgencyLogoPath(); + if (StringUtils.isNotBlank(agencyLogoPath)) { + Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("agency_logo.png")); //NON-NLS + } + + //copy navigation html + try (InputStream navStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/navigation.html")) { //NON-NLS + Files.copy(navStream, reportFolderPath.resolve("nav.html")); //NON-NLS + } + //copy favicon + try (InputStream faviconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico")) { //NON-NLS + Files.copy(faviconStream, reportFolderPath.resolve("favicon.ico")); //NON-NLS + } + //copy report summary icon + try (InputStream summaryStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png")) { //NON-NLS + Files.copy(summaryStream, reportFolderPath.resolve("summary.png")); //NON-NLS + } + //copy snapshot icon + try (InputStream snapshotIconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/images/image.png")) { //NON-NLS + Files.copy(snapshotIconStream, reportFolderPath.resolve("snapshot_icon.png")); //NON-NLS + } + //copy main report css + try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/index.css")) { //NON-NLS + Files.copy(resource, reportFolderPath.resolve("index.css")); //NON-NLS + } + //copy summary css + try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/summary.css")) { //NON-NLS + Files.copy(resource, reportFolderPath.resolve("summary.css")); //NON-NLS + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css new file mode 100644 index 0000000000..cbb4947792 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css @@ -0,0 +1,19 @@ +body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;} +#snapshot{max-width:800; max-height:600} +#content {padding: 30px;} +#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;} +h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;} +h2 {font-size: 20px; font-weight: bolder; color: #07A;} +h3 {font-size: 16px; color: #07A;} +h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;} +ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;} +ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;} +ul li a:hover {text-decoration: underline;} +p {margin: 0 0 20px 0;} +table {white-space:nowrap; min-width: 800px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;} +.keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;} +table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;} +table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; } +table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; } +table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; } +table tr:nth-child(even) td {background: #f3f3f3;} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html new file mode 100644 index 0000000000..11a0620ca4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html @@ -0,0 +1,14 @@ + + + Autopsy Report for {{curentCase.getName}} + + + + + + + Your browser is not compatible with our frame setup.<br /> + Please see <a href="nav.html">the navigation page</a> for links,<br /> + and <a href="summary.html">the summary page</a> for a case summary. + + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html new file mode 100644 index 0000000000..02b6705cfb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html @@ -0,0 +1,16 @@ + + + Report Navigation + + + + +
    +

    Report Navigation

    + +
    + + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html new file mode 100644 index 0000000000..eb237679a1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html @@ -0,0 +1,72 @@ + + + Timeline Snapshot: {{reportTitle}} + + + + +
    + Timeline Snapshot + + + + + {{#zoomParams.getFilter}} + + + + + {{/zoomParams.getFilter}} +
    Time Range: {{startTime}} + to + {{endTime}}
    Description Level of Detail: {{zoomParams.getDescriptionLOD.getDisplayName}}
    Event Type Zoom Level: {{zoomParams.getTypeZoomLevel.getDisplayName}}
    Filters: +
      + {{#getTextFilter}}
    • text = "{{getText}}" [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • {{/getTextFilter}} + {{#getKnownFilter}}
    • Hide Known Files [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • {{/getKnownFilter}} + {{#getDataSourcesFilter}} +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] : +
        + {{#getSubFilters}} +
      • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
      • + {{/getSubFilters}} +
      +
    • + {{/getDataSourcesFilter}} + {{#getTagsFilter}} +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] : +
        + {{#getSubFilters}} +
      • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
      • + {{/getSubFilters}} +
      +
    • + {{/getTagsFilter}} + {{#getHashHitsFilter}} +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] : +
        + {{#getSubFilters}} +
      • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
      • + {{/getSubFilters}} +
      +
    • + {{/getHashHitsFilter}} + {{#getTypeFilter}} +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] : +
        + {{#getSubFilters}} +
      • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] +
          + {{#getSubFilters}} +
        • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
        • + {{/getSubFilters}} +
        +
      • + {{/getSubFilters}} +
      +
    • + {{/getTypeFilter}} +
    +
    +
    + + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary.css b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary.css new file mode 100644 index 0000000000..489c19079a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary.css @@ -0,0 +1,14 @@ +body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; } +#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; } +h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; } +h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;} +h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; } +table td { padding-right: 25px; } +p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; } +.title { width: 660px; margin-bottom: 50px; } +.left { float: left; width: 250px; margin-top: 20px; text-align: center; } +.left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; } +.right { float: right; width: 385px; margin-top: 25px; font-size: 14px; } +.clear { clear: both; } +.info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; } +.info table { margin: 0 25px 20px 25px; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html new file mode 100644 index 0000000000..963c6d2096 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html @@ -0,0 +1,55 @@ + + + Case Summary + + + + + +
    +

    {{reportBranding.getReportTitle}}: {{reportName}}{{#ingestRunning}}Warning, this report was run before ingest services completed!{{/ingestRunning}}

    + +

    Timeline Report generated on {{generationDateTime}}

    +
    + {{#reportBranding.getAgencyLogoPath}} +
    + +
    +
    + {{/reportBranding.getAgencyLogoPath}} + {{^reportBranding.getAgencyLogoPath}} +
    + {{/reportBranding.getAgencyLogoPath}} + + + + + +
    Case:{{currentCase.getName}}
    Case Number:{{currentCase.getCaseNumber}}{{^currentCase.getCaseNumber}}No case number{{/currentCase.getCaseNumber}}
    Examiner:{{currentCase.getExaminer}}{{^currentCase.getExaminer}}No examiner{{/currentCase.getExaminer}}
    Number of Images:{{currentCase.getDataSources.size}}
    +
    +
    +
    +

    Image Information:

    + {{#currentCase.getDataSources}} +

    {{getName}}

    + {{#getTimeZone}} + + + {{#getPaths}} + + {{/getPaths}} +
    Timezone:{{getTimeZone}}
    Path:{{toString}}
    + {{/getTimeZone}} + {{/currentCase.getDataSources}} +
    + {{#reportBranding.getGeneratorLogoPath}} +
    + +
    + {{/reportBranding.getGeneratorLogoPath}} +
    + {{#reportBranding.getReportFooter}} +

    {{toString}}

    + {{/reportBranding.getReportFooter}} +
    + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java index ef29b563af..3b10982070 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-15 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.ui; import com.google.common.eventbus.Subscribe; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -29,26 +30,34 @@ import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; -import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.concurrent.Service; +import javafx.collections.transformation.SortedList; import javafx.concurrent.Task; import javafx.geometry.Pos; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.chart.Axis; -import javafx.scene.chart.Chart; import javafx.scene.chart.XYChart; import javafx.scene.control.Label; import javafx.scene.control.OverrunStyle; import javafx.scene.control.Tooltip; -import javafx.scene.effect.Effect; +import javafx.scene.layout.Border; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; +import javafx.scene.layout.BorderWidths; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.text.Text; @@ -56,7 +65,6 @@ import javafx.scene.text.TextAlignment; import javax.annotation.concurrent.Immutable; import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.MaskerPane; -import org.joda.time.Interval; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; @@ -67,153 +75,258 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; /** - * Abstract base class for {@link Chart} based {@link TimeLineView}s used in the - * main visualization area. + * Abstract base class for TimeLineChart based visualizations. * - * @param the type of data plotted along the x axis - * @param the type of data plotted along the y axis - * @param the type of nodes used to represent data items - * @param the type of the {@link XYChart} this class uses to - * plot the data. + * @param The type of data plotted along the x axis + * @param The type of data plotted along the y axis + * @param The type of nodes used to represent data items + * @param The type of the TimeLineChart this class uses to plot + * the data. Must extend Region. * * TODO: this is becoming (too?) closely tied to the notion that their is a - * {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up - * common history context menu items out of derived classes? -jm + * XYChart doing the rendering. Is this a good idea? -jm + * + * TODO: pull up common history context menu items out of derived classes? -jm */ public abstract class AbstractVisualizationPane> extends BorderPane { - @NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.") - private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text()); private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName()); + @NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.") + private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text()); + private static final Border ONLY_LEFT_BORDER = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 0, 0, 1))); + + /** + * Get the tool tip to use for this visualization when no more specific + * tooltip is needed. + * + * @return The default tooltip. + */ public static Tooltip getDefaultTooltip() { return DEFAULT_TOOLTIP; } - protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true); + private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true); - /** + /* * access to chart data via series */ protected final ObservableList> dataSeries = FXCollections.>observableArrayList(); protected final Map> eventTypeToSeriesMap = new HashMap<>(); - protected ChartType chart; + private ChartType chart; //// replacement axis label componenets - private final Pane leafPane; // container for the leaf lables in the declutterd axis - private final Pane branchPane;// container for the branch lables in the declutterd axis - protected final Region spacer; + private final Pane specificLabelPane = new Pane(); // container for the specfic labels in the decluttered axis + private final Pane contextLabelPane = new Pane();// container for the contextual labels in the decluttered axis + private final Region spacer = new Region(); /** * task used to reload the content of this visualization */ private Task updateTask; - final protected TimeLineController controller; + final private TimeLineController controller; + final private FilteredEventsModel filteredEvents; - final protected FilteredEventsModel filteredEvents; + final private ObservableList selectedNodes = FXCollections.observableArrayList(); - final protected ObservableList selectedNodes = FXCollections.observableArrayList(); + private InvalidationListener updateListener = any -> update(); - private InvalidationListener invalidationListener = (Observable observable) -> { - update(); - }; - - public ObservableList getSelectedNodes() { + /** + * The visualization nodes that are selected. + * + * @return An ObservableList of the nodes that are selected in + * this visualization. + */ + protected ObservableList getSelectedNodes() { return selectedNodes; } /** - * list of {@link Node}s to insert into the toolbar. This should be set in - * an implementations constructor. + * List of Nodes to insert into the toolbar. This should be set in an + * implementations constructor. */ - protected List settingsNodes; - - public TimeLineController getController() { - return controller; - } + private List settingsNodes; /** - * @return the list of nodes containing settings widgets to insert into this - * visualization's header + * Get a List of nodes containing settings widgets to insert into this + * visualization's header. + * + * @return The List of settings Nodes. */ protected List getSettingsNodes() { return Collections.unmodifiableList(settingsNodes); } /** - * @param value a value along this visualization's x axis + * Set the List of nodes containing settings widgets to insert into this + * visualization's header. * - * @return true if the tick label for the given value should be bold ( has - * relevant data), false* otherwise + * + * @param settingsNodes The List of nodes containing settings widgets to + * insert into this visualization's header. + */ + protected void setSettingsNodes(List settingsNodes) { + this.settingsNodes = new ArrayList<>(settingsNodes); + } + + /** + * Get the TimelineController for this visualization. + * + * @return The TimelineController for this visualization. + */ + protected TimeLineController getController() { + return controller; + } + + /** + * Get the CharType that implements this visualization. + * + * @return The CharType that implements this visualization. + */ + protected ChartType getChart() { + return chart; + } + + /** + * Get the FilteredEventsModel for this visualization. + * + * @return The FilteredEventsModel for this visualization. + */ + protected FilteredEventsModel getEventsModel() { + return filteredEvents; + } + + /** + * Set the ChartType that implements this visualization. + * + * @param chart The ChartType that implements this visualization. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + protected void setChart(ChartType chart) { + this.chart = chart; + setCenter(chart); + } + + /** + * A property that indicates whether there are any events visible in this + * visualization with the current view parameters. + * + * @return A property that indicates whether there are any events visible in + * this visualization with the current view parameters. + */ + ReadOnlyBooleanProperty hasVisibleEventsProperty() { + return hasVisibleEvents.getReadOnlyProperty(); + } + + /** + * Apply this visualization's 'selection effect' to the given node. + * + * @param node The node to apply the 'effect' to. + */ + protected void applySelectionEffect(NodeType node) { + applySelectionEffect(node, true); + } + + /** + * Remove this visualization's 'selection effect' from the given node. + * + * @param node The node to remvoe the 'effect' from. + */ + protected void removeSelectionEffect(NodeType node) { + applySelectionEffect(node, Boolean.FALSE); + } + + /** + * Should the tick mark at the given value be bold, because it has + * interesting data associated with it? + * + * @param value A value along this visualization's x axis + * + * @return True if the tick label for the given value should be bold ( has + * relevant data), false otherwise */ abstract protected Boolean isTickBold(X value); /** - * apply this visualization's 'selection effect' to the given node + * Apply this visualization's 'selection effect' to the given node, if + * applied is true. If applied is false, remove the affect * - * @param node the node to apply the 'effect' to - * @param applied true if the effect should be applied, false if the effect - * should + * @param node The node to apply the 'effect' to + * @param applied True if the effect should be applied, false if the effect + * should not */ abstract protected void applySelectionEffect(NodeType node, Boolean applied); /** - * @return a task to execute on a background thread to reload this + * Get a new background Task that fetches the appropriate data and loads it + * into this visualization. + * + * @return A new task to execute on a background thread to reload this * visualization with different data. */ - abstract protected Task getUpdateTask(); + abstract protected Task getNewUpdateTask(); /** - * @return return the {@link Effect} applied to 'selected nodes' in this - * visualization, or null if selection is visualized via another - * mechanism - */ - abstract protected Effect getSelectionEffect(); - - /** - * @param tickValue + * Get the label that should be used for a tick mark at the given value. * - * @return a String to use for a tick mark label given a tick value + * @param tickValue The value to get a label for. + * + * @return a String to use for a tick mark label given a tick value. */ abstract protected String getTickMarkLabel(X tickValue); /** - * the spacing (in pixels) between tick marks of the horizontal axis. This - * will be used to layout the decluttered replacement labels. + * Get the spacing, in pixels, between tick marks of the horizontal axis. + * This will be used to layout the decluttered replacement labels. * - * @return the spacing in pixels between tick marks of the horizontal axis + * @return The spacing, in pixels, between tick marks of the horizontal axis */ abstract protected double getTickSpacing(); /** - * @return the horizontal axis used by this Visualization's chart + * Get the X-Axis of this Visualization's chart + * + * @return The horizontal axis used by this Visualization's chart */ abstract protected Axis getXAxis(); /** - * @return the vertical axis used by this Visualization's chart + * Get the Y-Axis of this Visualization's chart + * + * @return The vertical axis used by this Visualization's chart */ abstract protected Axis getYAxis(); - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - abstract protected void resetData(); + /** + * Get the total amount of space (in pixels) the x-axis uses to pad the left + * and right sides. This value is used to keep decluttered axis aligned + * correctly. + * + * @return The x-axis margin (in pixels) + */ + abstract protected double getAxisMargin(); /** - * update this visualization based on current state of zoom / filters. - * Primarily this invokes the background {@link VisualizationUpdateTask} - * returned by {@link #getUpdateTask()}, which derived classes must - * implement. - * - * TODO: replace this logic with a {@link Service} ? -jm + * Clear all data items from this chart. */ - final synchronized public void update() { + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + abstract protected void clearChartData(); + + /** + * Update this visualization based on current state of zoom / filters. + * Primarily this invokes the background VisualizationUpdateTask returned by + * getUpdateTask(), which derived classes must implement. + * + * TODO: replace this logic with a javafx Service ? -jm + */ + protected final synchronized void update() { if (updateTask != null) { updateTask.cancel(true); updateTask = null; } - updateTask = getUpdateTask(); + updateTask = getNewUpdateTask(); updateTask.stateProperty().addListener((Observable observable) -> { switch (updateTask.getState()) { case CANCELLED: @@ -224,9 +337,9 @@ public abstract class AbstractVisualizationPane getSeries(final EventType et) { return eventTypeToSeriesMap.get(et); } - protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) { + /** + * Constructor + * + * @param controller The TimelineController for this visualization. + */ + protected AbstractVisualizationPane(TimeLineController controller) { this.controller = controller; this.filteredEvents = controller.getEventsModel(); this.filteredEvents.registerForEvents(this); - this.filteredEvents.zoomParametersProperty().addListener(invalidationListener); - this.leafPane = partPane; - this.branchPane = contextPane; - this.spacer = spacer; + this.filteredEvents.zoomParametersProperty().addListener(updateListener); + Platform.runLater(() -> { + VBox vBox = new VBox(specificLabelPane, contextLabelPane); + vBox.setFillWidth(false); + HBox hBox = new HBox(spacer, vBox); + hBox.setFillHeight(false); + setBottom(hBox); + DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin()); + spacer.minWidthProperty().bind(spacerSize); + spacer.prefWidthProperty().bind(spacerSize); + spacer.maxWidthProperty().bind(spacerSize); + }); createSeries(); - selectedNodes.addListener((ListChangeListener.Change c) -> { - while (c.next()) { - c.getRemoved().forEach(n -> applySelectionEffect(n, false)); - c.getAddedSubList().forEach(n -> applySelectionEffect(n, true)); + selectedNodes.addListener((ListChangeListener.Change change) -> { + while (change.next()) { + change.getRemoved().forEach(node -> applySelectionEffect(node, false)); + change.getAddedSubList().forEach(node -> applySelectionEffect(node, true)); } }); - TimeLineController.getTimeZone().addListener(invalidationListener); + TimeLineController.getTimeZone().addListener(updateListener); //show tooltip text in status bar - hoverProperty().addListener(observable -> controller.setStatus(isHover() ? DEFAULT_TOOLTIP.getText() : "")); + hoverProperty().addListener(hoverProp -> controller.setStatus(isHover() ? DEFAULT_TOOLTIP.getText() : "")); } + /** + * Handle a RefreshRequestedEvent from the events model by updating the + * visualization. + * + * @param event The RefreshRequestedEvent to handle. + */ @Subscribe public void handleRefreshRequested(RefreshRequestedEvent event) { update(); } /** - * iterate through the list of tick-marks building a two level structure of - * replacement tick marl labels. (Visually) upper level has most - * detailed/highest frequency part of date/time. Second level has rest of - * date/time grouped by unchanging part. eg: + * Iterate through the list of tick-marks building a two level structure of + * replacement tick mark labels. (Visually) upper level has most + * detailed/highest frequency part of date/time (specific label). Second + * level has rest of date/time grouped by unchanging part (contextual + * label). * + * eg: * - * october-30_october-31_september-01_september-02_september-03 + * October-October-31_September-01_September-02_September-03 * - * becomes + * becomes: * * _________30_________31___________01___________02___________03 * - * _________october___________|_____________september___________ + * _________October___________|_____________September___________ * */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - public synchronized void layoutDateLabels() { - + protected synchronized void layoutDateLabels() { //clear old labels - branchPane.getChildren().clear(); - leafPane.getChildren().clear(); + contextLabelPane.getChildren().clear(); + specificLabelPane.getChildren().clear(); //since the tickmarks aren't necessarily in value/position order, - //make a clone of the list sorted by position along axis - ObservableList> tickMarks = FXCollections.observableArrayList(getXAxis().getTickMarks()); - tickMarks.sort(Comparator.comparing(Axis.TickMark::getPosition)); + //make a copy of the list sorted by position along axis + SortedList> tickMarks = getXAxis().getTickMarks().sorted(Comparator.comparing(Axis.TickMark::getPosition)); if (tickMarks.isEmpty() == false) { //get the spacing between ticks in the underlying axis @@ -328,53 +471,50 @@ public abstract class AbstractVisualizationPane t : tickMarks) { - assignLeafLabel(new TwoPartDateTime(getTickMarkLabel(t.getValue())).leaf, + addSpecificLabel(new TwoPartDateTime(getTickMarkLabel(t.getValue())).specifics, spacing, - leafLabelX, + specificLabelX, isTickBold(t.getValue()) ); - - leafLabelX += spacing; //increment x + specificLabelX += spacing; //increment x } } else { //there are two parts so ... //initialize additional state - double branchLabelX = 0; - double branchLabelWidth = 0; - - for (Axis.TickMark t : tickMarks) { //for each tick + double contextLabelX = 0; + double contextLabelWidth = 0; + for (Axis.TickMark t : tickMarks) { //split the label into a TwoPartDateTime dateTime = new TwoPartDateTime(getTickMarkLabel(t.getValue())); - //if we are still on the same branch - if (lastSeenBranchLabel.equals(dateTime.branch)) { - //increment branch width - branchLabelWidth += spacing; - } else {// we are on to a new branch, so ... - assignBranchLabel(lastSeenBranchLabel, branchLabelWidth, branchLabelX); + //if we are still in the same context + if (lastSeenContextLabel.equals(dateTime.context)) { + //increment context width + contextLabelWidth += spacing; + } else {// we are on to a new context, so ... + addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX); //and then update label, x-pos, and width - lastSeenBranchLabel = dateTime.branch; - branchLabelX += branchLabelWidth; - branchLabelWidth = spacing; + lastSeenContextLabel = dateTime.context; + contextLabelX += contextLabelWidth; + contextLabelWidth = spacing; } - //add the label for the leaf (highest frequency part) - assignLeafLabel(dateTime.leaf, spacing, leafLabelX, isTickBold(t.getValue())); + //add the specific label (highest frequency part) + addSpecificLabel(dateTime.specifics, spacing, specificLabelX, isTickBold(t.getValue())); - //increment leaf position - leafLabelX += spacing; + //increment specific position + specificLabelX += spacing; } - //we have reached end so add branch label for current branch - assignBranchLabel(lastSeenBranchLabel, branchLabelWidth, branchLabelX); + //we have reached end so add label for current context + addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX); } } //request layout since we have modified scene graph structure @@ -382,46 +522,48 @@ public abstract class AbstractVisualizationPane the type of data displayed along the X-Axis. + * @param The type of a single object that can represent + * the range of data displayed along the X-Axis. */ abstract protected class VisualizationUpdateTask extends LoggedTask { + private final Node center; + + /** + * Constructor + * + * @param taskName The name of this task. + * @param logStateChanges Whether or not task state chanes should be + * logged. + */ protected VisualizationUpdateTask(String taskName, boolean logStateChanges) { super(taskName, logStateChanges); + this.center = getCenter(); } /** @@ -500,9 +653,10 @@ public abstract class AbstractVisualizationPane { - setCenter(center); //clear masker pane - setCursor(Cursor.DEFAULT); - }); + cleanup(); } /** - * Clears the chart data and sets the horisontal axis range. For use + * Removes the blocking progress indicator. Derived Tasks should be sure + * to call this as part of their cancelled() implementation. + */ + @Override + protected void cancelled() { + super.cancelled(); + cleanup(); + } + + /** + * Removes the blocking progress indicator. Derived Tasks should be sure + * to call this as part of their failed() implementation. + */ + @Override + protected void failed() { + super.failed(); + cleanup(); + } + + /** + * Removes the blocking progress indicator and reset the cursor to the + * default. + */ + private void cleanup() { + setCenter(center); //clear masker pane installed in call() + setCursor(Cursor.DEFAULT); + } + + /** + * Clears the chart data and sets the horizontal axis range. For use * within the derived implementation of the call() method. * * @param axisValues */ @ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI) protected void resetChart(AxisValuesType axisValues) { - Platform.runLater(() -> { - resetData(); + clearChartData(); setDateAxisValues(axisValues); }); } + /** + * Set the horizontal range that this chart will show. + * + * @param values A single object representing the range that this chart + * will show. + */ abstract protected void setDateAxisValues(AxisValuesType values); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml index ac2748668b..218aa21c58 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml @@ -1,15 +1,23 @@ - - - - - - + + + + + + + + + + + + + + + - - + @@ -78,17 +86,6 @@ - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java index 1fe72b5aa8..7463aafc2f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,7 +51,6 @@ import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BorderPane; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import static javafx.scene.layout.Region.USE_PREF_SIZE; @@ -141,14 +140,6 @@ final public class VisualizationPanel extends BorderPane { @FXML private Label endLabel; - //// replacemetn axis label componenets - @FXML - private Pane partPane; - @FXML - private Pane contextPane; - @FXML - private Region spacer; - //// header toolbar componenets @FXML private ToolBar toolBar; @@ -288,7 +279,7 @@ final public class VisualizationPanel extends BorderPane { setViewMode(controller.viewModeProperty().get()); //configure snapshor button / action - ActionUtils.configureButton(new SaveSnapshotAsReport(controller, VisualizationPanel.this), snapShotButton); + ActionUtils.configureButton(new SaveSnapshotAsReport(controller, notificationPane::getContent), snapShotButton); /////configure start and end pickers startLabel.setText(Bundle.VisualizationPanel_startLabel_text()); @@ -359,11 +350,11 @@ final public class VisualizationPanel extends BorderPane { private void setViewMode(VisualizationMode visualizationMode) { switch (visualizationMode) { case COUNTS: - setVisualization(new CountsViewPane(controller, partPane, contextPane, spacer)); + setVisualization(new CountsViewPane(controller)); countsToggle.setSelected(true); break; case DETAIL: - setVisualization(new DetailViewPane(controller, partPane, contextPane, spacer)); + setVisualization(new DetailViewPane(controller)); detailsToggle.setSelected(true); break; } @@ -388,7 +379,7 @@ final public class VisualizationPanel extends BorderPane { eventsTree.setDetailViewPane((DetailViewPane) visualization); }); } - visualization.hasEvents.addListener((observable, oldValue, newValue) -> { + visualization.hasVisibleEventsProperty().addListener((observable, oldValue, newValue) -> { if (newValue == false) { notificationPane.setContent( @@ -427,54 +418,54 @@ final public class VisualizationPanel extends BorderPane { histogramTask = new LoggedTask( NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS - private final Lighting lighting = new Lighting(); + private final Lighting lighting = new Lighting(); @Override protected Void call() throws Exception { - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS - long max = 0; - final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval()); - final long lowerBound = rangeInfo.getLowerBound(); - final long upperBound = rangeInfo.getUpperBound(); - Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone())); + long max = 0; + final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval()); + final long lowerBound = rangeInfo.getLowerBound(); + final long upperBound = rangeInfo.getUpperBound(); + Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone())); - //extend range to block bounderies (ie day, month, year) - int p = 0; // progress counter + //extend range to block bounderies (ie day, month, year) + int p = 0; // progress counter - //clear old data, and reset ranges and series - Platform.runLater(() -> { - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS + //clear old data, and reset ranges and series + Platform.runLater(() -> { + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS - }); + }); - ArrayList bins = new ArrayList<>(); + ArrayList bins = new ArrayList<>(); - DateTime start = timeRange.getStart(); - while (timeRange.contains(start)) { - if (isCancelled()) { - return null; - } - DateTime end = start.plus(rangeInfo.getPeriodSize().getPeriod()); - final Interval interval = new Interval(start, end); - //increment for next iteration + DateTime start = timeRange.getStart(); + while (timeRange.contains(start)) { + if (isCancelled()) { + return null; + } + DateTime end = start.plus(rangeInfo.getPeriodSize().getPeriod()); + final Interval interval = new Interval(start, end); + //increment for next iteration - start = end; + start = end; - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS - //query for current range - long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum(); - bins.add(count); + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS + //query for current range + long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum(); + bins.add(count); - max = Math.max(count, max); + max = Math.max(count, max); - final double fMax = Math.log(max); - final ArrayList fbins = new ArrayList<>(bins); - Platform.runLater(() -> { - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS + final double fMax = Math.log(max); + final ArrayList fbins = new ArrayList<>(bins); + Platform.runLater(() -> { + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS - histogramBox.getChildren().clear(); + histogramBox.getChildren().clear(); for (Long bin : fbins) { if (isCancelled()) { @@ -499,7 +490,7 @@ final public class VisualizationPanel extends BorderPane { return null; } - }; + }; new Thread(histogramTask).start(); controller.monitorTask(histogramTask); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/Bundle.properties deleted file mode 100644 index 2b4965f421..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/Bundle.properties +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-15 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. - */ -#this is the label for the vetical axis -CountsChartPane.numberOfEvents=Number of Events - -CountsViewPane.scaleLabel.text=Scale\: -CountsViewPane.logRadio.text=Logarithmic -CountsViewPane.linearRadio.text=Linear diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/Bundle_ja.properties index 9cbcb05152..e60d46f934 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/Bundle_ja.properties @@ -1,14 +1,14 @@ -CountsChartPane.numberOfEvents=\u30A4\u30D9\u30F3\u30C8\u6570 -CountsViewPane.detailSwitchMessage=\u79D2\u3088\u308A\u5C0F\u3055\u3044\u5358\u4F4D\u306F\u3042\u308A\u307E\u305B\u3093\u3002\n\u8A73\u7D30\u30D3\u30E5\u30FC\u306B\u5909\u66F4\u3057\u307E\u3059\u304B\uFF1F -CountsViewPane.detailSwitchTitle=\u8A73\u7D30\u30D3\u30E5\u30FC\u306B\u5909\u66F4\u3057\u307E\u3059\u304B\uFF1F -Timeline.ui.countsview.menuItem.selectEventType=\u30A4\u30D9\u30F3\u30C8\u30BF\u30A4\u30D7\u3092\u9078\u629E -Timeline.ui.countsview.menuItem.selectTimeandType=\u6642\u9593\u3068\u30BF\u30A4\u30D7\u3092\u9078\u629E -Timeline.ui.countsview.menuItem.selectTimeRange=\u6642\u9593\u7BC4\u56F2\u3092\u9078\u629E -Timeline.ui.countsview.menuItem.zoomIntoTimeRange=\u6642\u9593\u7BC4\u56F2\u3078\u30BA\u30FC\u30E0\u30A4\u30F3 -CountsViewPane.loggedTask.name=\u30AB\u30A6\u30F3\u30C8\u30D3\u30E5\u30FC\u3092\u66F4\u65B0\u4E2D -CountsViewPane.loggedTask.updatingCounts=\u30D3\u30B8\u30E5\u30A2\u30E9\u30A4\u30BC\u30FC\u30B7\u30E7\u30F3\uFF08\u53EF\u8996\u5316\uFF09\u3092\u5165\u529B\u4E2D -CountsViewPane.tooltip.text={0} {1} \u30A4\u30D9\u30F3\u30C8\n{2}\u3068\n{3}\u306E\u9593 -CountsViewPane.linearRadio.text=\u30EA\u30CB\u30A2 -CountsViewPane.logRadio.text=\u5BFE\u6570\u7684 -CountsViewPane.scaleLabel.text=\u30B9\u30B1\u30FC\u30EB\uFF1A -*=Autopsy\u30D5\u30A9\u30EC\u30F3\u30B8\u30C3\u30AF\u30D6\u30E9\u30A6\u30B6 \ No newline at end of file +CountsViewPane.numberOfEvents=\u30a4\u30d9\u30f3\u30c8\u6570 +CountsViewPane.detailSwitchMessage=\u79d2\u3088\u308a\u5c0f\u3055\u3044\u5358\u4f4d\u306f\u3042\u308a\u307e\u305b\u3093\u3002\n\u8a73\u7d30\u30d3\u30e5\u30fc\u306b\u5909\u66f4\u3057\u307e\u3059\u304b\uff1f +CountsViewPane.detailSwitchTitle=\u8a73\u7d30\u30d3\u30e5\u30fc\u306b\u5909\u66f4\u3057\u307e\u3059\u304b\uff1f +Timeline.ui.countsview.menuItem.selectEventType=\u30a4\u30d9\u30f3\u30c8\u30bf\u30a4\u30d7\u3092\u9078\u629e +Timeline.ui.countsview.menuItem.selectTimeandType=\u6642\u9593\u3068\u30bf\u30a4\u30d7\u3092\u9078\u629e +Timeline.ui.countsview.menuItem.selectTimeRange=\u6642\u9593\u7bc4\u56f2\u3092\u9078\u629e +Timeline.ui.countsview.menuItem.zoomIntoTimeRange=\u6642\u9593\u7bc4\u56f2\u3078\u30ba\u30fc\u30e0\u30a4\u30f3 +CountsViewPane.loggedTask.name=\u30ab\u30a6\u30f3\u30c8\u30d3\u30e5\u30fc\u3092\u66f4\u65b0\u4e2d +CountsViewPane.loggedTask.updatingCounts=\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\uff08\u53ef\u8996\u5316\uff09\u3092\u5165\u529b\u4e2d +CountsViewPane.tooltip.text={0} {1} \u30a4\u30d9\u30f3\u30c8\n{2}\u3068\n{3}\u306e\u9593 +CountsViewPane.linearRadio.text=\u30ea\u30cb\u30a2 +CountsViewPane.logRadio.text=\u5bfe\u6570\u7684 +CountsViewPane.scaleLabel.text=\u30b9\u30b1\u30fc\u30eb\uff1a +*=Autopsy\u30d5\u30a9\u30ec\u30f3\u30b8\u30c3\u30af\u30d6\u30e9\u30a6\u30b6 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java index 8d37e68e33..91649dcf37 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,29 +19,36 @@ package org.sleuthkit.autopsy.timeline.ui.countsview; import com.google.common.collect.Lists; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Function; import javafx.application.Platform; import javafx.beans.Observable; +import javafx.beans.binding.BooleanBinding; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.concurrent.Task; import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.NumberAxis; -import javafx.scene.chart.StackedBarChart; import javafx.scene.chart.XYChart; import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.ToggleGroup; import javafx.scene.control.Tooltip; -import javafx.scene.effect.Effect; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; -import javafx.scene.layout.Region; +import javafx.scene.text.Font; +import javafx.scene.text.FontPosture; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import org.controlsfx.control.PopOver; import org.joda.time.Interval; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; @@ -51,29 +58,24 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane; -import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; +import static org.sleuthkit.autopsy.timeline.ui.countsview.Bundle.*; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; /** - * FXML Controller class for a {@link StackedBarChart} based - * implementation of a {@link TimeLineView}. + * FXML Controller class for a StackedBarChart based + * implementation of a TimeLineChart. * - * This class listens to changes in the assigned {@link FilteredEventsModel} and - * updates the internal {@link StackedBarChart} to reflect the currently - * requested events. + * This class listens to changes in the assigned FilteredEventsModel and updates + * the internal EventCountsChart to reflect the currently requested events. * * This class captures input from the user in the form of mouse clicks on graph - * bars, and forwards them to the assigned {@link TimeLineController} * + * bars, and forwards them to the assigned TimeLineController * * Concurrency Policy: Access to the private members stackedBarChart, countAxis, * dateAxis, EventTypeMap, and dataSets affects the stackedBarChart so they all - * must only be manipulated on the JavaFx thread (through {@link Platform#runLater(java.lang.Runnable)} - * - * {@link CountsChartPane#filteredEvents} should encapsulate all need - * synchronization internally. - * - * TODO: refactor common code out of this class and {@link DetailViewPane} into - * {@link AbstractVisualizationPane} + * must only be manipulated on the JavaFx thread (through + * Platform.runLater(java.lang.Runnable). The FilteredEventsModel should + * encapsulate all need synchronization internally. */ public class CountsViewPane extends AbstractVisualizationPane { @@ -82,7 +84,7 @@ public class CountsViewPane extends AbstractVisualizationPaneobservableArrayList()); - private final SimpleObjectProperty scale = new SimpleObjectProperty<>(ScaleType.LOGARITHMIC); + private final SimpleObjectProperty scaleProp = new SimpleObjectProperty<>(Scale.LOGARITHMIC); @Override protected String getTickMarkLabel(String labelValueString) { @@ -96,51 +98,53 @@ public class CountsViewPane extends AbstractVisualizationPane getUpdateTask() { + protected Task getNewUpdateTask() { return new CountsUpdateTask(); } - public CountsViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) { - super(controller, partPane, contextPane, spacer); - chart = new EventCountsChart(controller, dateAxis, countAxis, selectedNodes); + /** + * Constructor + * + * @param controller The TimelineController for this visualization. + * @param specificPane The container for the specific axis labels. + * @param contextPane The container for the contextual axis labels. + * @param spacer The Region to use as a spacer to keep the axis labels + * aligned. + */ + @NbBundle.Messages({ + "# {0} - scale name", + "CountsViewPane.numberOfEvents=Number of Events ({0})"}) + public CountsViewPane(TimeLineController controller) { + super(controller); + setChart(new EventCountsChart(controller, dateAxis, countAxis, getSelectedNodes())); + getChart().setData(dataSeries); + Tooltip.install(getChart(), getDefaultTooltip()); - chart.setData(dataSeries); - setCenter(chart); + setSettingsNodes(new CountsViewSettingsPane().getChildrenUnmodifiable()); - Tooltip.install(chart, getDefaultTooltip()); + dateAxis.getTickMarks().addListener((Observable tickMarks) -> layoutDateLabels()); + dateAxis.categorySpacingProperty().addListener((Observable spacing) -> layoutDateLabels()); + dateAxis.getCategories().addListener((Observable categories) -> layoutDateLabels()); - settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable()); - - dateAxis.getTickMarks().addListener((Observable observable) -> { - layoutDateLabels(); - }); - dateAxis.categorySpacingProperty().addListener((Observable observable) -> { - layoutDateLabels(); - }); - dateAxis.getCategories().addListener((Observable observable) -> { - layoutDateLabels(); - }); - - spacer.minWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2))); - spacer.prefWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2))); - spacer.maxWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2))); - - scale.addListener(o -> { - countAxis.tickLabelsVisibleProperty().bind(scale.isEqualTo(ScaleType.LINEAR)); - countAxis.tickMarkVisibleProperty().bind(scale.isEqualTo(ScaleType.LINEAR)); - countAxis.minorTickVisibleProperty().bind(scale.isEqualTo(ScaleType.LINEAR)); + //bind tick visibility to scaleProp + BooleanBinding scaleIsLinear = scaleProp.isEqualTo(Scale.LINEAR); + countAxis.tickLabelsVisibleProperty().bind(scaleIsLinear); + countAxis.tickMarkVisibleProperty().bind(scaleIsLinear); + countAxis.minorTickVisibleProperty().bind(scaleIsLinear); + scaleProp.addListener(scale -> { update(); + syncAxisScaleLabel(); }); - + syncAxisScaleLabel(); } @Override - protected NumberAxis getYAxis() { + final protected NumberAxis getYAxis() { return countAxis; } @Override - protected CategoryAxis getXAxis() { + final protected CategoryAxis getXAxis() { return dateAxis; } @@ -150,28 +154,86 @@ public class CountsViewPane extends AbstractVisualizationPane series : dataSeries) { + series.getData().clear(); + } + dataSeries.clear(); + eventTypeToSeriesMap.clear(); + createSeries(); + } + /** + * Set the appropriate label on the vertical axis, depending on the selected + * scale. + */ + private void syncAxisScaleLabel() { + countAxis.setLabel(Bundle.CountsViewPane_numberOfEvents(scaleProp.get().getDisplayName())); + } + + /** + * Enum for the Scales available in the Counts View. + */ + @NbBundle.Messages({ + "ScaleType.Linear=Linear", + "ScaleType.Logarithmic=Logarithmic"}) + private static enum Scale implements Function { + + LINEAR(Bundle.ScaleType_Linear()) { + @Override + public Double apply(Long inValue) { + return inValue.doubleValue(); + } + }, + LOGARITHMIC(Bundle.ScaleType_Logarithmic()) { + @Override + public Double apply(Long inValue) { + return Math.log10(inValue) + 1; + } + }; + + private final String displayName; + + /** + * Constructor + * + * @param displayName The display name for this Scale. + */ + Scale(String displayName) { + this.displayName = displayName; + } + + /** + * Get the display name of this ScaleType + * + * @return The display name. + */ + private String getDisplayName() { + return displayName; } } + @Override + protected double getAxisMargin() { + return dateAxis.getStartMargin() + dateAxis.getEndMargin(); + } + + /* + * A Pane that contains widgets to adjust settings specific to a + * CountsViewPane + */ private class CountsViewSettingsPane extends HBox { @FXML private RadioButton logRadio; - @FXML private RadioButton linearRadio; - @FXML private ToggleGroup scaleGroup; @@ -179,58 +241,68 @@ public class CountsViewPane extends AbstractVisualizationPane { if (scaleGroup.getSelectedToggle() == linearRadio) { - scale.set(ScaleType.LINEAR); - } - if (scaleGroup.getSelectedToggle() == logRadio) { - scale.set(ScaleType.LOGARITHMIC); + scaleProp.set(Scale.LINEAR); + } else if (scaleGroup.getSelectedToggle() == logRadio) { + scaleProp.set(Scale.LOGARITHMIC); } }); + logRadio.setSelected(true); - logRadio.setText(NbBundle.getMessage(CountsViewPane.class, "CountsViewPane.logRadio.text")); - linearRadio.setText(NbBundle.getMessage(CountsViewPane.class, "CountsViewPane.linearRadio.text")); - scaleLabel.setText(NbBundle.getMessage(CountsViewPane.class, "CountsViewPane.scaleLabel.text")); + //make a popup hrlp window with descriptions of the scales. + helpImageView.setCursor(Cursor.HAND); + helpImageView.setOnMouseClicked(clicked -> { + Text text = new Text(Bundle.CountsViewPane_scaleHelp()); + Text text2 = new Text(Bundle.CountsViewPane_scaleHelp2()); + Font baseFont = text.getFont(); + text2.setFont(Font.font(baseFont.getFamily(), FontWeight.BOLD, FontPosture.ITALIC, baseFont.getSize())); + Text text3 = new Text(Bundle.CountsViewPane_scaleHelp3()); + + Pane borderPane = new BorderPane(null, null, new ImageView(helpImageView.getImage()), + new TextFlow(text, text2, text3), + new Label(Bundle.CountsViewPane_scaleHelp_label_text())); + borderPane.setPadding(new Insets(10)); + borderPane.setPrefWidth(500); + + PopOver popOver = new PopOver(borderPane); + popOver.setDetachable(false); + popOver.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER); + popOver.show(helpImageView); + }); } + /** + * Constructor + */ CountsViewSettingsPane() { FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS } } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @Override - protected void resetData() { - for (XYChart.Series s : dataSeries) { - s.getData().clear(); - } - - dataSeries.clear(); - eventTypeToSeriesMap.clear(); - createSeries(); - } - - private static enum ScaleType implements Function { - - LINEAR(Long::doubleValue), - LOGARITHMIC(t -> Math.log10(t) + 1); - - private final Function func; - - ScaleType(Function func) { - this.func = func; - } - - @Override - public Double apply(Long t) { - return func.apply(t); - } - } - + /** + * Task that clears the Chart, fetches new data according to the current + * ZoomParams and loads it into the Chart + * + */ @NbBundle.Messages({ "CountsViewPane.loggedTask.name=Updating Counts View", "CountsViewPane.loggedTask.updatingCounts=Populating visualization"}) @@ -246,27 +318,25 @@ public class CountsViewPane extends AbstractVisualizationPane intervals = rangeInfo.getIntervals(); - List categories = Lists.transform(intervals, rangeInfo::formatForTick); //clear old data, and reset ranges and series - resetChart(categories); + resetChart(Lists.transform(intervals, rangeInfo::formatForTick)); updateMessage(Bundle.CountsViewPane_loggedTask_updatingCounts()); int chartMax = 0; int numIntervals = intervals.size(); + Scale activeScale = scaleProp.get(); + /* - * for each interval query database for event counts and add to - * chart. - * - * Doing this in chunks might seem inefficient but it lets us reuse - * more cached results as the user navigates to overlapping viewws - * - * //TODO: implement similar chunked caching in DetailsView -jm + * For each interval, query the database for event counts and add + * the counts to the chart. Doing this in chunks might seem + * inefficient but it lets us reuse more cached results as the user + * navigates to overlapping views. */ for (int i = 0; i < numIntervals; i++) { if (isCancelled()) { @@ -277,7 +347,7 @@ public class CountsViewPane extends AbstractVisualizationPane eventCounts = filteredEvents.getEventCounts(interval); + Map eventCounts = eventsModel.getEventCounts(interval); //for each type add data to graph for (final EventType eventType : eventCounts.keySet()) { @@ -288,7 +358,7 @@ public class CountsViewPane extends AbstractVisualizationPane 0) { final String intervalCategory = rangeInfo.formatForTick(interval); - final double adjustedCount = scale.get().apply(count); + final double adjustedCount = activeScale.apply(count); final XYChart.Data dataItem = new XYChart.Data<>(intervalCategory, adjustedCount, @@ -299,9 +369,10 @@ public class CountsViewPane extends AbstractVisualizationPane { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewSettingsPane.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewSettingsPane.fxml index 87c506f386..06bc6c8779 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewSettingsPane.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewSettingsPane.fxml @@ -1,28 +1,37 @@ - - - - + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/EventCountsChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/EventCountsChart.java index 26569ab47c..477ef7eb64 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/EventCountsChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/EventCountsChart.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-15 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -87,7 +87,6 @@ final class EventCountsChart extends StackedBarChart implements dateAxis.setTickLabelsVisible(false); dateAxis.setTickLabelGap(0); - countAxis.setLabel(NbBundle.getMessage(CountsViewPane.class, "CountsChartPane.numberOfEvents")); countAxis.setAutoRanging(false); countAxis.setLowerBound(0); countAxis.setAnimated(true); @@ -167,6 +166,7 @@ final class EventCountsChart extends StackedBarChart implements return new CountsIntervalSelector(this); } + @Override public ObservableList getSelectedNodes() { return selectedNodes; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 9527a1f2cd..5b9dc12837 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview; -import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; @@ -40,10 +39,7 @@ import javafx.scene.control.RadioButton; import javafx.scene.control.Slider; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleGroup; -import javafx.scene.effect.Effect; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Region; import javafx.stage.Modality; import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.action.Action; @@ -55,10 +51,9 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane; -import org.sleuthkit.autopsy.timeline.ui.detailview.HideDescriptionAction; -import org.sleuthkit.autopsy.timeline.ui.detailview.UnhideDescriptionAction; import org.sleuthkit.autopsy.timeline.utils.MappedList; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; @@ -103,29 +98,25 @@ public class DetailViewPane extends AbstractVisualizationPane(getSelectedNodes(), EventNodeBase::getEvent); //initialize chart; - chart = new DetailsChart(controller, detailsChartDateAxis, pinnedDateAxis, verticalAxis, getSelectedNodes()); - setCenter(chart); - settingsNodes = new ArrayList<>(new DetailViewSettingsPane(chart.getLayoutSettings()).getChildrenUnmodifiable()); + setChart(new DetailsChart(controller, detailsChartDateAxis, pinnedDateAxis, verticalAxis, getSelectedNodes())); + setSettingsNodes(new DetailViewSettingsPane(getChart().getLayoutSettings()).getChildrenUnmodifiable()); //bind layout fo axes and spacers detailsChartDateAxis.getTickMarks().addListener((Observable observable) -> layoutDateLabels()); detailsChartDateAxis.getTickSpacing().addListener(observable -> layoutDateLabels()); verticalAxis.setAutoRanging(false); //prevent XYChart.updateAxisRange() from accessing dataSeries on JFX thread causing ConcurrentModificationException - bottomLeftSpacer.minWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty())); - bottomLeftSpacer.prefWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty())); - bottomLeftSpacer.maxWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty())); - selectedNodes.addListener((Observable observable) -> { + getSelectedNodes().addListener((Observable observable) -> { //update selected nodes highlight - chart.setHighlightPredicate(selectedNodes::contains); + getChart().setHighlightPredicate(getSelectedNodes()::contains); //update controllers list of selected event ids when view's selection changes. - getController().selectEventIDs(selectedNodes.stream() + getController().selectEventIDs(getSelectedNodes().stream() .flatMap(detailNode -> detailNode.getEventIDs().stream()) .collect(Collectors.toList())); }); @@ -137,7 +128,7 @@ public class DetailViewPane extends AbstractVisualizationPane getAllNestedEvents() { - return chart.getAllNestedEvents(); + return getChart().getAllNestedEvents(); } /* @@ -169,13 +160,13 @@ public class DetailViewPane extends AbstractVisualizationPane StringUtils.equalsIgnoreCase(eventNode.getDescription(), description); } })// => predicates that match strings agains the descriptions of the events in highlightedEvents - .reduce(selectedNodes::contains, Predicate::or); // => predicate that matches an of the descriptions or selected nodes - chart.setHighlightPredicate(highlightPredicate); //use this predicate to highlight nodes + .reduce(getSelectedNodes()::contains, Predicate::or); // => predicate that matches an of the descriptions or selected nodes + getChart().setHighlightPredicate(highlightPredicate); //use this predicate to highlight nodes }); } @Override - public Axis getXAxis() { + final protected DateAxis getXAxis() { return detailsChartDateAxis; } @@ -188,7 +179,7 @@ public class DetailViewPane extends AbstractVisualizationPane getYAxis() { + final protected Axis getYAxis() { return verticalAxis; } @@ -230,20 +221,20 @@ public class DetailViewPane extends AbstractVisualizationPane getUpdateTask() { + protected Task getNewUpdateTask() { return new DetailsUpdateTask(); } - @Override - protected Effect getSelectionEffect() { - return null; - } - @Override protected void applySelectionEffect(EventNodeBase c1, Boolean selected) { c1.applySelectionEffect(selected); } + @Override + protected double getAxisMargin() { + return 0; + } + /** * A Pane that contains widgets to adjust settings specific to a * DetailViewPane @@ -365,14 +356,15 @@ public class DetailViewPane extends AbstractVisualizationPane eventStripes = filteredEvents.getEventStripes(); + List eventStripes = eventsModel.getEventStripes(); final int size = eventStripes.size(); //if there are too many stipes show a confirmation dialog if (size > 2000) { @@ -407,7 +399,7 @@ public class DetailViewPane extends AbstractVisualizationPane chart.addStripe(stripe)); + Platform.runLater(() -> getChart().addStripe(stripe)); } return eventStripes.isEmpty() == false; @@ -416,7 +408,7 @@ public class DetailViewPane extends AbstractVisualizationPane { - //handle changes in the model property - Platform.runLater(() -> { - //remove listener to avoid circular updates - slider.valueProperty().removeListener(sliderListener); - slider.valueChangingProperty().removeListener(sliderListener); + //remove listener to avoid circular updates + slider.valueProperty().removeListener(sliderListener); + slider.valueChangingProperty().removeListener(sliderListener); + Platform.runLater(() -> { //sync value of slider to model property value slider.setValue(driverValueMapper.apply(modelProperty.get())); diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index 0f71c74744..70bbe38e46 100644 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -43,6 +43,8 @@ + + @@ -63,5 +65,7 @@ + + diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 35f2e8e8c6..7b1b68be8a 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -15,6 +15,7 @@ file.reference.commons-lang3-3.0.jar=release/modules/ext/commons-lang3-3.0.jar file.reference.commons-logging-1.1.2-javadoc.jar=release/modules/ext/commons-logging-1.1.2-javadoc.jar file.reference.commons-logging-1.1.2-sources.jar=release/modules/ext/commons-logging-1.1.2-sources.jar file.reference.commons-logging-1.1.2.jar=release/modules/ext/commons-logging-1.1.2.jar +file.reference.compiler-0.9.1.jar=release/modules/ext/compiler-0.9.1.jar file.reference.controlsfx-8.40.10.jar=release/modules/ext/controlsfx-8.40.10.jar file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar @@ -70,12 +71,14 @@ file.reference.xml-apis-1.0.b2.jar=release/modules/ext/xml-apis-1.0.b2.jar file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial +javadoc.reference.compiler-0.9.1.jar=release/modules/ext/compiler-0.9.1-javadoc.jar javadoc.reference.controlsfx-8.40.10.jar=release/modules/ext/controlsfx-8.40.10-javadoc.jar javadoc.reference.guava-18.0.jar=release/modules/ext/guava-18.0-javadoc.jar javadoc.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-javadoc.jar javadoc.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4-javadoc.jar javadoc.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-javadoc.jar nbm.needs.restart=true +source.reference.compiler-0.9.1.jar=release/modules/ext/compiler-0.9.1-sources.jar source.reference.controlsfx-8.40.10.jar=release/modules/ext/controlsfx-8.40.10-sources.jar source.reference.guava-18.0.jar=release/modules/ext/guava-18.0-sources.jar source.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-sources.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index bc106e6d83..f1e05e398c 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -29,6 +29,13 @@ com.apple.eawt com.apple.eawt.event com.apple.eio + com.github.mustachejava + com.github.mustachejava.codes + com.github.mustachejava.functions + com.github.mustachejava.reflect + com.github.mustachejava.reflect.guards + com.github.mustachejava.resolver + com.github.mustachejava.util com.google.common.annotations com.google.common.base com.google.common.cache @@ -643,6 +650,10 @@ ext/commons-logging-1.1.2-sources.jar release/modules/ext/commons-logging-1.1.2-sources.jar + + ext/imageio-thumbsdb-3.2.jar + release/modules/ext/imageio-thumbsdb-3.2.jar + ext/ant-launcher-1.8.2.jar release/modules/ext/ant-launcher-1.8.2.jar @@ -651,18 +662,22 @@ ext/logkit-1.0.1.jar release/modules/ext/logkit-1.0.1.jar - - ext/imageio-bmp-3.2.jar - release/modules/ext/imageio-bmp-3.2.jar - - - ext/imageio-pcx-3.2.jar - release/modules/ext/imageio-pcx-3.2.jar - ext/imageio-jpeg-3.2.jar release/modules/ext/imageio-jpeg-3.2.jar + + ext/compiler-0.9.1.jar + release/modules/ext/compiler-0.9.1.jar + + + ext/imageio-iff-3.2.jar + release/modules/ext/imageio-iff-3.2.jar + + + ext/imageio-icns-3.2.jar + release/modules/ext/imageio-icns-3.2.jar + ext/imgscalr-lib-4.2-javadoc.jar release/modules/ext/imgscalr-lib-4.2-javadoc.jar @@ -707,6 +722,10 @@ ext/imgscalr-lib-4.2.jar release/modules/ext/imgscalr-lib-4.2.jar + + ext/common-io-3.2.jar + release/modules/ext/common-io-3.2.jar + ext/commons-lang3-3.0-sources.jar release/modules/ext/commons-lang3-3.0-sources.jar @@ -716,21 +735,17 @@ release/modules/ext/mail-1.4.3.jar - ext/imageio-tiff-3.2.jar - release/modules/ext/imageio-tiff-3.2.jar + ext/guava-18.0.jar + release/modules/ext/guava-18.0.jar + + + ext/imageio-tga-3.2.jar + release/modules/ext/imageio-tga-3.2.jar ext/imageio-pnm-3.2.jar release/modules/ext/imageio-pnm-3.2.jar - - ext/common-lang-3.2.jar - release/modules/ext/common-lang-3.2.jar - - - ext/guava-18.0.jar - release/modules/ext/guava-18.0.jar - ext/slf4j-api-1.6.1.jar release/modules/ext/slf4j-api-1.6.1.jar @@ -751,6 +766,10 @@ ext/poi-ooxml-3.8.jar release/modules/ext/poi-ooxml-3.8.jar + + ext/imageio-psd-3.2.jar + release/modules/ext/imageio-psd-3.2.jar + ext/stax-api-1.0.1.jar release/modules/ext/stax-api-1.0.1.jar @@ -764,8 +783,8 @@ release/modules/ext/poi-excelant-3.8.jar - ext/common-image-3.2.jar - release/modules/ext/common-image-3.2.jar + ext/imageio-pcx-3.2.jar + release/modules/ext/imageio-pcx-3.2.jar ext/log4j-1.2.17.jar @@ -779,10 +798,18 @@ ext/avalon-framework-4.1.5.jar release/modules/ext/avalon-framework-4.1.5.jar + + ext/imageio-tiff-3.2.jar + release/modules/ext/imageio-tiff-3.2.jar + ext/jfxtras-controls-8.0-r4.jar release/modules/ext/jfxtras-controls-8.0-r4.jar + + ext/imageio-sgi-3.2.jar + release/modules/ext/imageio-sgi-3.2.jar + ext/geronimo-jms_1.1_spec-1.0.jar release/modules/ext/geronimo-jms_1.1_spec-1.0.jar @@ -803,10 +830,6 @@ ext/joda-time-2.4.jar release/modules/ext/joda-time-2.4.jar - - ext/common-io-3.2.jar - release/modules/ext/common-io-3.2.jar - ext/commons-logging-1.1.2-javadoc.jar release/modules/ext/commons-logging-1.1.2-javadoc.jar @@ -815,6 +838,10 @@ ext/slf4j-simple-1.6.1.jar release/modules/ext/slf4j-simple-1.6.1.jar + + ext/imageio-bmp-3.2.jar + release/modules/ext/imageio-bmp-3.2.jar + ext/commons-lang3-3.0-javadoc.jar release/modules/ext/commons-lang3-3.0-javadoc.jar @@ -839,78 +866,62 @@ ext/commons-codec-1.5.jar release/modules/ext/commons-codec-1.5.jar - - ext/imageio-icns-3.2.jar - release/modules/ext/imageio-icns-3.2.jar - ext/javassist-3.12.1.GA.jar release/modules/ext/javassist-3.12.1.GA.jar - - ext/commons-logging-1.1.2.jar - release/modules/ext/commons-logging-1.1.2.jar - - - ext/imageio-psd-3.2.jar - release/modules/ext/imageio-psd-3.2.jar - - - ext/commons-io-2.4.jar - release/modules/ext/commons-io-2.4.jar - - - ext/imageio-sgi-3.2.jar - release/modules/ext/imageio-sgi-3.2.jar - - - ext/poi-3.8.jar - release/modules/ext/poi-3.8.jar - - - ext/imageio-thumbsdb-3.2.jar - release/modules/ext/imageio-thumbsdb-3.2.jar - - - ext/commons-lang3-3.0.jar - release/modules/ext/commons-lang3-3.0.jar - - - ext/imageio-iff-3.2.jar - release/modules/ext/imageio-iff-3.2.jar - - - ext/imageio-tga-3.2.jar - release/modules/ext/imageio-tga-3.2.jar - ext/imageio-core-3.2.jar release/modules/ext/imageio-core-3.2.jar + + ext/commons-logging-1.1.2.jar + release/modules/ext/commons-logging-1.1.2.jar + + + ext/commons-io-2.4.jar + release/modules/ext/commons-io-2.4.jar + + + ext/poi-3.8.jar + release/modules/ext/poi-3.8.jar + + + ext/commons-lang3-3.0.jar + release/modules/ext/commons-lang3-3.0.jar + ext/javaee-api-5.0-2.jar release/modules/ext/javaee-api-5.0-2.jar - ext/gstreamer-java-1.5.jar - release/modules/ext/gstreamer-java-1.5.jar - - - ext/imageio-metadata-3.2.jar - release/modules/ext/imageio-metadata-3.2.jar - - - ext/controlsfx-8.40.10.jar - release/modules/ext/controlsfx-8.40.10.jar + ext/common-image-3.2.jar + release/modules/ext/common-image-3.2.jar ext/imageio-pict-3.2.jar release/modules/ext/imageio-pict-3.2.jar + + ext/common-lang-3.2.jar + release/modules/ext/common-lang-3.2.jar + + + ext/gstreamer-java-1.5.jar + release/modules/ext/gstreamer-java-1.5.jar + + + ext/controlsfx-8.40.10.jar + release/modules/ext/controlsfx-8.40.10.jar + ext/dom4j-1.6.1.jar release/modules/ext/dom4j-1.6.1.jar + + ext/imageio-metadata-3.2.jar + release/modules/ext/imageio-metadata-3.2.jar + ext/imgscalr-lib-4.2-sources.jar release/modules/ext/imgscalr-lib-4.2-sources.jar diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form index df0173a112..50aa22c94a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form @@ -313,6 +313,9 @@ + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java index 2af3f77da7..78ff768518 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java @@ -23,6 +23,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +40,7 @@ import javax.swing.event.ListSelectionListener; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; +import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; @@ -52,6 +54,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis private static final Logger logger = Logger.getLogger(GlobalEditListPanel.class.getName()); private KeywordTableModel tableModel; private KeywordList currentKeywordList; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** * Creates new form GlobalEditListPanel @@ -141,6 +144,16 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis }); } + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + void setButtonStates() { boolean isIngestRunning = IngestManager.getInstance().isIngestRunning(); boolean isListSelected = currentKeywordList != null; @@ -297,6 +310,11 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis deleteListButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/delete16.png"))); // NOI18N deleteListButton.setText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "KeywordSearchEditListPanel.deleteListButton.text")); // NOI18N + deleteListButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteListButtonActionPerformed(evt); + } + }); saveListButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/save16.png"))); // NOI18N saveListButton.setText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "KeywordSearchEditListPanel.saveListButton.text")); // NOI18N @@ -423,6 +441,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis XmlKeywordSearchList.getCurrent().addList(currentKeywordList); chRegex.setSelected(false); addWordField.setText(""); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); setButtonStates(); }//GEN-LAST:event_addWordButtonActionPerformed @@ -433,6 +452,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis tableModel.deleteSelected(keywordTable.getSelectedRows()); XmlKeywordSearchList.getCurrent().addList(currentKeywordList); setButtonStates(); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } }//GEN-LAST:event_deleteWordButtonActionPerformed @@ -495,7 +515,13 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis currentKeywordList.setIngestMessages(ingestMessagesCheckbox.isSelected()); XmlKeywordSearchList updater = XmlKeywordSearchList.getCurrent(); updater.addList(currentKeywordList); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_ingestMessagesCheckboxActionPerformed + + private void deleteListButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteListButtonActionPerformed + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_deleteListButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel addKeywordPanel; private javax.swing.JButton addWordButton; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java index 62bcecda24..764017891d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.keywordsearch; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; import java.util.List; import javax.swing.JOptionPane; import org.openide.util.NbBundle; @@ -109,6 +110,18 @@ final class GlobalListSettingsPanel extends javax.swing.JPanel implements Option mainSplitPane.revalidate(); mainSplitPane.repaint(); } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + listsManagementPanel.addPropertyChangeListener(l); + editListPanel.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + listsManagementPanel.removePropertyChangeListener(l); + editListPanel.removePropertyChangeListener(l); + } @Override public void store() { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java index 05920d6d9c..c9297e5ff1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java @@ -19,17 +19,20 @@ package org.sleuthkit.autopsy.keywordsearch; import java.awt.event.KeyEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.File; import java.util.ArrayList; import java.util.List; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.event.ListSelectionListener; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.AbstractTableModel; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.autopsy.coreutils.Logger; /** * A panel to manage all keyword lists created/imported in Autopsy. @@ -38,6 +41,7 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa private Logger logger = Logger.getLogger(GlobalListsManagementPanel.class.getName()); private KeywordListTableModel tableModel; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); GlobalListsManagementPanel() { tableModel = new KeywordListTableModel(); @@ -74,6 +78,16 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa * listsTable.getSelectionModel().clearSelection(); } } } }); */ } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } /** * This method is called from within the constructor to initialize the form. @@ -197,6 +211,7 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa listsTable.getSelectionModel().addSelectionInterval(i, i); } } + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_newListButtonActionPerformed private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed @@ -291,6 +306,7 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa } } } + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_importButtonActionPerformed private void listsTableKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_listsTableKeyPressed if (evt.getKeyCode() == KeyEvent.VK_DELETE) { @@ -300,6 +316,7 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa } else if (KeywordSearchUtil.displayConfirmDialog(NbBundle.getMessage(this.getClass(), "KeywordSearchConfigurationPanel1.customizeComponents.title"), NbBundle.getMessage(this.getClass(), "KeywordSearchConfigurationPanel1.customizeComponents.body"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN)) { String listName = (String) listsTable.getModel().getValueAt(selected[0], 0); XmlKeywordSearchList.getCurrent().deleteList(listName); + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } else { return; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java index b88ce6de59..03e72a8ddb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java @@ -21,11 +21,14 @@ package org.sleuthkit.autopsy.keywordsearch; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JCheckBox; +import org.netbeans.spi.options.OptionsPanelController; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.StringExtract; import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT; @@ -39,6 +42,7 @@ class KeywordSearchGlobalLanguageSettingsPanel extends javax.swing.JPanel implem private final Map scripts = new HashMap<>(); private ActionListener updateLanguagesAction; private List