Merge remote-tracking branch 'upstream/develop' into 2146-3

This commit is contained in:
Oliver Spohngellert 2016-06-13 10:56:09 -04:00
commit e21eb3de48
58 changed files with 2857 additions and 2297 deletions

View File

@ -99,7 +99,7 @@ class AddImageTask implements Runnable {
List<String> errorMessages = new ArrayList<>();
List<Content> newDataSources = new ArrayList<>();
try {
currentCase.getSleuthkitCase().acquireExclusiveLock();
currentCase.getSleuthkitCase().acquireExclusiveLockForSQLite();
synchronized (tskAddImageProcessLock) {
tskAddImageProcess = currentCase.makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles);
}
@ -112,7 +112,7 @@ class AddImageTask implements Runnable {
commitOrRevertAddImageProcess(currentCase, errorMessages, newDataSources);
progressMonitor.setProgress(100);
} finally {
currentCase.getSleuthkitCase().releaseExclusiveLock();
currentCase.getSleuthkitCase().releaseExclusiveLockForSQLite();
DataSourceProcessorCallback.DataSourceProcessorResult result;
if (criticalErrorOccurred) {
result = DataSourceProcessorResult.CRITICAL_ERRORS;

View File

@ -46,7 +46,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.apache.commons.io.FileUtils;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
@ -76,6 +75,7 @@ import org.sleuthkit.autopsy.events.AutopsyEventException;
import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
@ -1519,28 +1519,22 @@ public class Case implements SleuthkitCase.ErrorObserver {
if (RuntimeProperties.coreComponentsAreActive()) {
// enable these menus
CallableSystemAction.get(AddImageAction.class).setEnabled(true);
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu
if (toChangeTo.hasData()) {
// open all top components
SwingUtilities.invokeLater(() -> {
CoreComponentControl.openCoreWindows();
});
} else {
// close all top components
SwingUtilities.invokeLater(() -> {
CoreComponentControl.closeCoreWindows();
});
}
}
if (RuntimeProperties.coreComponentsAreActive()) {
SwingUtilities.invokeLater(() -> {
updateMainWindowTitle(currentCase.getName());
CallableSystemAction.get(AddImageAction.class).setEnabled(true);
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
if (toChangeTo.hasData()) {
// open all top components
CoreComponentControl.openCoreWindows();
} else {
// close all top components
CoreComponentControl.closeCoreWindows();
}
});
updateMainWindowTitle(currentCase.getName());
} else {
SwingUtilities.invokeLater(() -> {
Frame f = WindowManager.getDefault().getMainWindow();
@ -1549,9 +1543,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
} else { // case is closed
if (RuntimeProperties.coreComponentsAreActive()) {
SwingUtilities.invokeLater(() -> {
if (RuntimeProperties.coreComponentsAreActive()) {
SwingUtilities.invokeLater(() -> {
// close all top components first
CoreComponentControl.closeCoreWindows();
@ -1560,15 +1554,11 @@ public class Case implements SleuthkitCase.ErrorObserver {
CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu
});
}
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
}
//clear pending notifications
SwingUtilities.invokeLater(() -> {
//clear pending notifications
MessageNotifyUtil.Notify.clear();
});
SwingUtilities.invokeLater(() -> {
Frame f = WindowManager.getDefault().getMainWindow();
f.setTitle(Case.getAppName()); // set the window name to just application name
});

View File

@ -40,10 +40,10 @@ import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskFileRange;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.CarvedFileContainer;
import org.sleuthkit.datamodel.LocalFilesDataSource;
import org.sleuthkit.datamodel.TskDataException;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.datamodel.CarvingResult;
/**
* A manager that provides methods for retrieving files from the current case
@ -324,45 +324,21 @@ public class FileManager implements Closeable {
}
/**
* Adds a carved file to the '$CarvedFiles' virtual directory of a data
* source, volume or file system.
* Adds a carving result to the case database.
*
* @param fileName The name of the file.
* @param fileSize The size of the file.
* @param parentObjId The object id of the parent data source, volume or
* file system.
* @param layout A list of the offsets and sizes that gives the layout
* of the file within its parent.
* @param carvingResult The carving result (a set of carved files and their
* parent) to be added.
*
* @return A LayoutFile object representing the carved file.
* @return A list of LayoutFile representations of the carved files.
*
* @throws TskCoreException if there is a problem adding the file to the
* case database.
* @throws TskCoreException If there is a problem completing a case database
* operation.
*/
public synchronized LayoutFile addCarvedFile(String fileName, long fileSize, long parentObjId, List<TskFileRange> layout) throws TskCoreException {
public synchronized List<LayoutFile> addCarvedFiles(CarvingResult carvingResult) throws TskCoreException {
if (null == caseDb) {
throw new TskCoreException("File manager has been closed");
}
return caseDb.addCarvedFile(fileName, fileSize, parentObjId, layout);
}
/**
* Adds a collection of carved files to the '$CarvedFiles' virtual directory
* of a data source, volume or file system.
*
* @param A collection of CarvedFileContainer objects, one per carved file,
* all of which must have the same parent object id.
*
* @return A collection of LayoutFile object representing the carved files.
*
* @throws TskCoreException if there is a problem adding the files to the
* case database.
*/
public synchronized List<LayoutFile> addCarvedFiles(List<CarvedFileContainer> filesToAdd) throws TskCoreException {
if (null == caseDb) {
throw new TskCoreException("File manager has been closed");
}
return caseDb.addCarvedFiles(filesToAdd);
return caseDb.addCarvedFiles(carvingResult);
}
/**
@ -552,6 +528,16 @@ public class FileManager implements Closeable {
}
}
/**
* Closes the file manager.
*
* @throws IOException If there is a problem closing the file manager.
*/
@Override
public synchronized void close() throws IOException {
caseDb = null;
}
/**
* Adds a set of local/logical files and/or directories to the case database
* as data source.
@ -583,13 +569,55 @@ public class FileManager implements Closeable {
}
/**
* Closes the file manager.
* Adds a carved file to the '$CarvedFiles' virtual directory of a data
* source, volume or file system.
*
* @throws IOException If there is a problem closing the file manager.
* @param fileName The name of the file.
* @param fileSize The size of the file.
* @param parentObjId The object id of the parent data source, volume or
* file system.
* @param layout A list of the offsets and sizes that gives the layout
* of the file within its parent.
*
* @return A LayoutFile object representing the carved file.
*
* @throws TskCoreException if there is a problem adding the file to the
* case database.
* @deprecated Use List<LayoutFile> addCarvedFiles(CarvingResult
* carvingResult instead.
*/
@Override
public synchronized void close() throws IOException {
caseDb = null;
@Deprecated
public synchronized LayoutFile addCarvedFile(String fileName, long fileSize, long parentObjId, List<TskFileRange> layout) throws TskCoreException {
if (null == caseDb) {
throw new TskCoreException("File manager has been closed");
}
Content parent = caseDb.getContentById(parentObjId);
List<CarvingResult.CarvedFile> carvedFiles = new ArrayList<>();
carvedFiles.add(new CarvingResult.CarvedFile(fileName, fileSize, layout));
List<LayoutFile> layoutFiles = caseDb.addCarvedFiles(new CarvingResult(parent, carvedFiles));
return layoutFiles.get(0);
}
/**
* Adds a collection of carved files to the '$CarvedFiles' virtual directory
* of a data source, volume or file system.
*
* @param A collection of CarvedFileContainer objects, one per carved file,
* all of which must have the same parent object id.
*
* @return A collection of LayoutFile object representing the carved files.
*
* @throws TskCoreException if there is a problem adding the files to the
* case database.
* @deprecated Use List<LayoutFile> addCarvedFiles(CarvingResult
* carvingResult instead.
*/
@Deprecated
public synchronized List<LayoutFile> addCarvedFiles(List<org.sleuthkit.datamodel.CarvedFileContainer> filesToAdd) throws TskCoreException {
if (null == caseDb) {
throw new TskCoreException("File manager has been closed");
}
return caseDb.addCarvedFiles(filesToAdd);
}
}

View File

@ -387,11 +387,11 @@
<file name="org-sleuthkit-autopsy-report-ReportWizardAction.shadow">
<attr name="displayName" bundlevalue="org.sleuthkit.autopsy.report.Bundle#Toolbars/Reports/org-sleuthkit-autopsy-report-ReportWizardAction.shadow"/>
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-report-ReportWizardAction.instance"/>
<attr name="position" intvalue="102"/>
<attr name="position" intvalue="103"/>
</file>
</folder>
<folder name="Ingest">
<attr intvalue="103" name="position"/>
<attr intvalue="104" name="position"/>
<file name="org-sleuthkit-autopsy-ingest-IngestMessagesAction.shadow">
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-ingest-IngestMessagesAction.instance"/>
</file>

View File

@ -3,7 +3,7 @@
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[5, 5]"/>
<Dimension value="[0, 5]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[5, 5]"/>
@ -56,6 +56,9 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.directoryTablePath.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[5, 14]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="numberMatchLabel">
@ -73,6 +76,11 @@
</Properties>
</Component>
<Container class="javax.swing.JTabbedPane" name="dataResultTabbedPanel">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 5]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
</Container>

View File

@ -544,26 +544,29 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
matchLabel = new javax.swing.JLabel();
dataResultTabbedPanel = new javax.swing.JTabbedPane();
setMinimumSize(new java.awt.Dimension(5, 5));
setMinimumSize(new java.awt.Dimension(0, 5));
setPreferredSize(new java.awt.Dimension(5, 5));
org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N
directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14));
org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N
dataResultTabbedPanel.setMinimumSize(new java.awt.Dimension(0, 5));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(directoryTablePath)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 518, Short.MAX_VALUE)
.addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(numberMatchLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(matchLabel))
.addComponent(dataResultTabbedPanel)
.addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -572,9 +575,9 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(numberMatchLabel)
.addComponent(matchLabel))
.addComponent(directoryTablePath))
.addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(0, 0, 0)
.addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 340, Short.MAX_VALUE))
.addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -147,7 +147,6 @@ HashsetHits.createSheet.name.name=Name
HashsetHits.createSheet.name.displayName=Name
HashsetHits.createSheet.name.desc=no description
ImageNode.getActions.viewInNewWin.text=View in New Window
ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes
ImageNode.createSheet.name.name=Name
ImageNode.createSheet.name.displayName=Name
ImageNode.createSheet.name.desc=no description

View File

@ -18,11 +18,14 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.awt.event.ActionEvent;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
@ -32,6 +35,8 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
@ -76,15 +81,25 @@ public class ImageNode extends AbstractContentNode<Image> {
* @return
*/
@Override
@Messages({"ImageNode.action.runIngestMods.text=Run Ingest Modules",
"ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes",})
public Action[] getActions(boolean context) {
List<Action> actionsList = new ArrayList<Action>();
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
actionsList.add(new FileSearchAction(
Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
actionsList.add(new AbstractAction(
Bundle.ImageNode_action_runIngestMods_text()) {
@Override
public void actionPerformed(ActionEvent e) {
final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(Collections.<Content>singletonList(content));
ingestDialog.display();
}
});
actionsList.add(new NewWindowViewAction(
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
actionsList.add(new FileSearchAction(
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.openFileSearchByAttr.text")));
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
return actionsList.toArray(new Action[0]);
}

View File

@ -18,13 +18,16 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.awt.event.ActionEvent;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
@ -33,7 +36,10 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ -80,13 +86,25 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode<VirtualDirect
* @return
*/
@Override
@NbBundle.Messages({"VirtualDirectoryNode.action.runIngestMods.text=Run Ingest Modules"})
public Action[] getActions(boolean popup) {
List<Action> actions = new ArrayList<>();
actions.add(new NewWindowViewAction(
NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.getActions.viewInNewWin.text"), this));
actions.add(null); // creates a menu separator
actions.add(ExtractAction.getInstance());
actions.add(null); // creates a menu separator
actions.add(new FileSearchAction(
Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
actions.add(new AbstractAction(
Bundle.VirtualDirectoryNode_action_runIngestMods_text()) {
@Override
public void actionPerformed(ActionEvent e) {
final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(Collections.<Content>singletonList(content));
ingestDialog.display();
}
});
actions.addAll(ContextMenuExtensionPoint.getActions());
return actions.toArray(new Action[0]);
}

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.filesearch;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
/**
@ -39,4 +40,9 @@ abstract class AbstractFileSearchFilter<T extends JComponent> implements FileSea
public T getComponent() {
return this.component;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
this.getComponent().addPropertyChangeListener(listener);
}
}

View File

@ -205,6 +205,11 @@ class DateSearchFilter extends AbstractFileSearchFilter<DateSearchPanel> {
getComponent().addActionListener(l);
}
@Override
public boolean isValid() {
return this.getComponent().isValidSearch();
}
/**
* Inner class to put the separator inside the combo box.
*/

View File

@ -236,6 +236,9 @@
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="DateSearchPanel.modifiedCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="modifiedCheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="changedCheckBox">
<Properties>
@ -244,6 +247,9 @@
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="DateSearchPanel.changedCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="changedCheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="accessedCheckBox">
<Properties>
@ -252,6 +258,9 @@
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="DateSearchPanel.accessedCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="accessedCheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="createdCheckBox">
<Properties>
@ -260,6 +269,9 @@
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="DateSearchPanel.createdCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="createdCheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="org.jbundle.thin.base.screen.jcalendarbutton.JCalendarButton" name="dateFromButtonCalendar">
<Properties>

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.filesearch;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
@ -37,6 +39,7 @@ class DateSearchPanel extends javax.swing.JPanel {
DateFormat dateFormat;
List<String> timeZones;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
DateSearchPanel(DateFormat dateFormat, List<String> timeZones) {
this.dateFormat = dateFormat;
@ -134,6 +137,16 @@ class DateSearchPanel extends javax.swing.JPanel {
this.createdCheckBox.setEnabled(enable);
}
@Override
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -209,15 +222,35 @@ class DateSearchPanel extends javax.swing.JPanel {
modifiedCheckBox.setSelected(true);
modifiedCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.modifiedCheckBox.text")); // NOI18N
modifiedCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
modifiedCheckBoxActionPerformed(evt);
}
});
changedCheckBox.setSelected(true);
changedCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.changedCheckBox.text")); // NOI18N
changedCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
changedCheckBoxActionPerformed(evt);
}
});
accessedCheckBox.setSelected(true);
accessedCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.accessedCheckBox.text")); // NOI18N
accessedCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
accessedCheckBoxActionPerformed(evt);
}
});
createdCheckBox.setSelected(true);
createdCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.createdCheckBox.text")); // NOI18N
createdCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
createdCheckBoxActionPerformed(evt);
}
});
dateFromButtonCalendar.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.dateFromButtonCalendar.text")); // NOI18N
dateFromButtonCalendar.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
@ -349,8 +382,25 @@ class DateSearchPanel extends javax.swing.JPanel {
private void dateCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dateCheckBoxActionPerformed
this.setComponentsEnabled();
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_dateCheckBoxActionPerformed
private void modifiedCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_modifiedCheckBoxActionPerformed
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_modifiedCheckBoxActionPerformed
private void accessedCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_accessedCheckBoxActionPerformed
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_accessedCheckBoxActionPerformed
private void createdCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createdCheckBoxActionPerformed
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_createdCheckBoxActionPerformed
private void changedCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_changedCheckBoxActionPerformed
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_changedCheckBoxActionPerformed
/**
* Validate and set the datetime field on the screen given a datetime
* string.
@ -379,6 +429,13 @@ class DateSearchPanel extends javax.swing.JPanel {
dateToTextField.setText(dateStringResult);
dateToButtonCalendar.setTargetDate(date);
}
boolean isValidSearch() {
return this.accessedCheckBox.isSelected() ||
this.changedCheckBox.isSelected() ||
this.createdCheckBox.isSelected() ||
this.modifiedCheckBox.isSelected();
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JCheckBox accessedCheckBox;
private javax.swing.JCheckBox changedCheckBox;

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.filesearch;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
/**
@ -40,6 +41,13 @@ interface FileSearchFilter {
*/
boolean isEnabled();
/**
* Checks if the panel has valid input for search.
*
* @return Whether the panel has valid input for search.
*/
boolean isValid();
/**
* Gets predicate expression to include in the SQL filter expression
*
@ -56,6 +64,13 @@ interface FileSearchFilter {
*/
void addActionListener(ActionListener l);
/**
* Adds the property change listener to the panel
*
* @param listener the listener to add.
*/
void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Thrown if a filter's inputs are invalid
*/

View File

@ -17,38 +17,35 @@
* limitations under the License.
*/
/*
/*
* FileSearchPanel.java
*
* Created on Mar 5, 2012, 1:51:50 PM
*/
package org.sleuthkit.autopsy.filesearch;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException;
import org.sleuthkit.datamodel.AbstractFile;
@ -64,6 +61,10 @@ class FileSearchPanel extends javax.swing.JPanel {
private static int resultWindowCount = 0; //keep track of result windows so they get unique names
private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, "FileSearchPanel.emptyWhereClause.text");
enum EVENT {
CHECKED
}
/**
* Creates new form FileSearchPanel
*/
@ -100,25 +101,39 @@ class FileSearchPanel extends javax.swing.JPanel {
filterPanel.add(fa);
}
for (FileSearchFilter filter : this.getFilters()) {
filter.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
searchButton.setEnabled(isValidSearch());
}
});
}
addListenerToAll(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
search();
}
});
searchButton.setEnabled(isValidSearch());
}
/**
* @return true if any of the filters in the panel are enabled (checked)
*/
private boolean anyFiltersEnabled() {
private boolean isValidSearch() {
boolean enabled = false;
for (FileSearchFilter filter : this.getFilters()) {
if (filter.isEnabled()) {
return true;
enabled = true;
if (!filter.isValid()) {
return false;
}
}
}
return false;
return enabled;
}
/**
@ -129,7 +144,7 @@ class FileSearchPanel extends javax.swing.JPanel {
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
if (this.anyFiltersEnabled()) {
if (this.isValidSearch()) {
String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount);
String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText");
@ -290,6 +305,7 @@ class FileSearchPanel extends javax.swing.JPanel {
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel filterPanel;
private javax.swing.JButton searchButton;

View File

@ -19,7 +19,6 @@
package org.sleuthkit.autopsy.filesearch;
import java.awt.event.ActionListener;
import org.openide.util.NbBundle;
import org.sleuthkit.datamodel.TskData.FileKnown;
@ -42,6 +41,7 @@ class KnownStatusSearchFilter extends AbstractFileSearchFilter<KnownStatusSearch
@Override
public boolean isEnabled() {
return this.getComponent().getKnownCheckBox().isSelected();
}
@Override
@ -83,4 +83,9 @@ class KnownStatusSearchFilter extends AbstractFileSearchFilter<KnownStatusSearch
@Override
public void addActionListener(ActionListener l) {
}
@Override
public boolean isValid() {
return this.getComponent().isValidSearch();
}
}

View File

@ -64,6 +64,9 @@
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="KnownStatusSearchPanel.unknownOptionCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="unknownOptionCheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="knownOptionCheckBox">
<Properties>
@ -83,6 +86,9 @@
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="KnownStatusSearchPanel.knownBadOptionCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="knownBadOptionCheckBoxActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -17,13 +17,15 @@
* limitations under the License.
*/
/*
/*
* KnownStatusSearchPanel.java
*
* Created on Oct 19, 2011, 11:45:44 AM
*/
package org.sleuthkit.autopsy.filesearch;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.JCheckBox;
/**
@ -32,6 +34,8 @@ import javax.swing.JCheckBox;
*/
class KnownStatusSearchPanel extends javax.swing.JPanel {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* Creates new form KnownStatusSearchPanel
*/
@ -63,6 +67,20 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
this.knownBadOptionCheckBox.setEnabled(enabled);
}
@Override
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
boolean isValidSearch() {
return this.unknownOptionCheckBox.isSelected() || this.knownBadOptionCheckBox.isSelected() || this.knownOptionCheckBox.isSelected();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -86,6 +104,11 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
unknownOptionCheckBox.setSelected(true);
unknownOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.unknownOptionCheckBox.text")); // NOI18N
unknownOptionCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
unknownOptionCheckBoxActionPerformed(evt);
}
});
knownOptionCheckBox.setSelected(true);
knownOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.knownOptionCheckBox.text")); // NOI18N
@ -97,6 +120,11 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
knownBadOptionCheckBox.setSelected(true);
knownBadOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.knownBadOptionCheckBox.text")); // NOI18N
knownBadOptionCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
knownBadOptionCheckBoxActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@ -127,13 +155,22 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
}// </editor-fold>//GEN-END:initComponents
private void knownOptionCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownOptionCheckBoxActionPerformed
// TODO add your handling code here:
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_knownOptionCheckBoxActionPerformed
private void knownCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownCheckBoxActionPerformed
setComponentsEnabled();
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_knownCheckBoxActionPerformed
private void unknownOptionCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_unknownOptionCheckBoxActionPerformed
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_unknownOptionCheckBoxActionPerformed
private void knownBadOptionCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadOptionCheckBoxActionPerformed
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_knownBadOptionCheckBoxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JCheckBox knownBadOptionCheckBox;
private javax.swing.JCheckBox knownCheckBox;

View File

@ -10,28 +10,28 @@ import java.awt.event.ActionListener;
/**
* Filter by mime type used in filter areas of file search by attribute.
*/
class MimeTypeFilter extends AbstractFileSearchFilter<MimeTypePanel> {
class MimeTypeFilter extends AbstractFileSearchFilter<MimeTypePanel> {
public MimeTypeFilter(MimeTypePanel component) {
super(component);
}
public MimeTypeFilter() {
this(new MimeTypePanel());
}
@Override
public boolean isEnabled() {
return this.getComponent().isSelected() &&
!this.getComponent().getMimeTypesSelected().isEmpty();
return this.getComponent().isSelected();
}
@Override
public String getPredicate() throws FilterValidationException {
String predicate = "";
for(String mimeType : this.getComponent().getMimeTypesSelected()) {
for (String mimeType : this.getComponent().getMimeTypesSelected()) {
predicate += "mime_type = '" + mimeType + "' OR ";
}
if(predicate.length() > 3) {
if (predicate.length() > 3) {
predicate = predicate.substring(0, predicate.length() - 3);
}
return predicate;
@ -41,4 +41,8 @@ class MimeTypeFilter extends AbstractFileSearchFilter<MimeTypePanel> {
public void addActionListener(ActionListener l) {
}
@Override
public boolean isValid() {
return !this.getComponent().getMimeTypesSelected().isEmpty();
}
}

View File

@ -31,7 +31,7 @@
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jScrollPane1" pref="298" max="32767" attributes="0"/>
<Component id="jScrollPane1" pref="0" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="jLabel1" min="-2" pref="246" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>

View File

@ -5,12 +5,16 @@
*/
package org.sleuthkit.autopsy.filesearch;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.logging.Level;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.tika.mime.MediaType;
import org.apache.tika.mime.MimeTypes;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -25,6 +29,7 @@ public class MimeTypePanel extends javax.swing.JPanel {
private static final SortedSet<MediaType> mediaTypes = MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes();
private static final Logger logger = Logger.getLogger(MimeTypePanel.class.getName());
private static final long serialVersionUID = 1L;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* Creates new form MimeTypePanel
@ -32,6 +37,12 @@ public class MimeTypePanel extends javax.swing.JPanel {
public MimeTypePanel() {
initComponents();
setComponentsEnabled();
this.mimeTypeList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
});
}
private String[] getMimeTypeArray() {
@ -76,6 +87,16 @@ public class MimeTypePanel extends javax.swing.JPanel {
this.jLabel1.setEnabled(enabled);
}
@Override
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -141,6 +162,8 @@ public class MimeTypePanel extends javax.swing.JPanel {
private void mimeTypeCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mimeTypeCheckBoxActionPerformed
setComponentsEnabled();
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
this.mimeTypeList.setSelectedIndices(new int[0]);
}//GEN-LAST:event_mimeTypeCheckBoxActionPerformed

View File

@ -19,7 +19,6 @@
package org.sleuthkit.autopsy.filesearch;
import java.awt.event.ActionListener;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException;
@ -63,4 +62,9 @@ class NameSearchFilter extends AbstractFileSearchFilter<NameSearchPanel> {
public void addActionListener(ActionListener l) {
getComponent().addActionListener(l);
}
@Override
public boolean isValid() {
return !this.getComponent().getSearchTextField().getText().isEmpty();
}
}

View File

@ -17,7 +17,7 @@
* limitations under the License.
*/
/*
/*
* NameSearchPanel.java
*
* Created on Oct 19, 2011, 11:58:53 AM
@ -26,9 +26,13 @@ package org.sleuthkit.autopsy.filesearch;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.JCheckBox;
import javax.swing.JMenuItem;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
*
@ -36,6 +40,8 @@ import javax.swing.JTextField;
*/
class NameSearchPanel extends javax.swing.JPanel {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* Creates new form NameSearchPanel
*/
@ -67,6 +73,22 @@ class NameSearchPanel extends javax.swing.JPanel {
copyMenuItem.addActionListener(actList);
pasteMenuItem.addActionListener(actList);
selectAllMenuItem.addActionListener(actList);
this.searchTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
@Override
public void removeUpdate(DocumentEvent e) {
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
@Override
public void changedUpdate(DocumentEvent e) {
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
});
}
@ -84,6 +106,16 @@ class NameSearchPanel extends javax.swing.JPanel {
this.noteNameLabel.setEnabled(enabled);
}
@Override
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -168,6 +200,7 @@ class NameSearchPanel extends javax.swing.JPanel {
private void nameCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nameCheckBoxActionPerformed
setComponentsEnabled();
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_nameCheckBoxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.filesearch;
import java.awt.event.ActionListener;
import javax.swing.JComboBox;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException;
@ -73,4 +72,9 @@ class SizeSearchFilter extends AbstractFileSearchFilter<SizeSearchPanel> {
public void addActionListener(ActionListener l) {
getComponent().addActionListener(l);
}
@Override
public boolean isValid() {
return true;
}
}

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.filesearch;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.NumberFormat;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
@ -32,6 +34,8 @@ import javax.swing.JMenuItem;
*/
class SizeSearchPanel extends javax.swing.JPanel {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* Creates new form SizeSearchPanel
*/
@ -89,6 +93,16 @@ class SizeSearchPanel extends javax.swing.JPanel {
this.sizeTextField.setEnabled(enabled);
}
@Override
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -168,6 +182,7 @@ class SizeSearchPanel extends javax.swing.JPanel {
private void sizeCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sizeCheckBoxActionPerformed
setComponentsEnabled();
pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_sizeCheckBoxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -182,12 +182,6 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
Path tempFilePath = null;
try {
long id = getRootId(file);
// make sure we have a valid systemID
if (id == -1) {
return IngestModule.ProcessResult.ERROR;
}
// Verify initialization succeeded.
if (null == this.executableFile) {
logger.log(Level.SEVERE, "PhotoRec carver called after failed start up"); // NON-NLS
@ -276,7 +270,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
// Now that we've cleaned up the folders and data files, parse the xml output file to add carved items into the database
long calcstart = System.currentTimeMillis();
PhotoRecCarverOutputParser parser = new PhotoRecCarverOutputParser(outputDirPath);
List<LayoutFile> carvedItems = parser.parse(newAuditFile, id, file);
List<LayoutFile> carvedItems = parser.parse(newAuditFile, file);
long calcdelta = (System.currentTimeMillis() - calcstart);
totals.totalParsetime.addAndGet(calcdelta);
if (carvedItems != null) { // if there were any results from carving, add the unallocated carving event to the reports list.
@ -411,31 +405,6 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
return path;
}
/**
* Finds the root Volume or Image of the AbstractFile passed in.
*
* @param file The file we want to find the root parent for
*
* @return The ID of the root parent Volume or Image
*/
private static long getRootId(AbstractFile file) {
long id = -1;
Content parent = null;
try {
parent = file.getParent();
while (parent != null) {
if (parent instanceof Volume || parent instanceof Image) {
id = parent.getId();
break;
}
parent = parent.getParent();
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "PhotoRec carver exception while trying to get parent of AbstractFile.", ex); //NON-NLS
}
return id;
}
/**
* Finds and returns the path to the executable, if able.
*

View File

@ -32,8 +32,8 @@ import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.CarvingResult;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.CarvedFileContainer;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskFileRange;
import org.w3c.dom.Document;
@ -70,7 +70,7 @@ class PhotoRecCarverOutputParser {
* @throws FileNotFoundException
* @throws IOException
*/
List<LayoutFile> parse(File xmlInputFile, long id, AbstractFile af) throws FileNotFoundException, IOException {
List<LayoutFile> parse(File xmlInputFile, AbstractFile af) throws FileNotFoundException, IOException {
try {
final Document doc = XMLUtil.loadDoc(PhotoRecCarverOutputParser.class, xmlInputFile.toString());
if (doc == null) {
@ -99,8 +99,7 @@ class PhotoRecCarverOutputParser {
FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
// create and initialize the list to put into the database
List<CarvedFileContainer> carvedFileContainer = new ArrayList<>();
List<CarvingResult.CarvedFile> carvedFiles = new ArrayList<>();
for (int fileIndex = 0; fileIndex < numberOfFiles; ++fileIndex) {
entry = (Element) fileObjects.item(fileIndex);
fileNames = entry.getElementsByTagName("filename"); //NON-NLS
@ -133,7 +132,7 @@ class PhotoRecCarverOutputParser {
if (fileByteEnd > af.getSize()) {
long overshoot = fileByteEnd - af.getSize();
if (fileSize > overshoot) {
fileSize = fileSize - overshoot;
fileSize -= overshoot;
} else {
// This better never happen... Data for this file is corrupted. Skip it.
continue;
@ -144,10 +143,10 @@ class PhotoRecCarverOutputParser {
}
if (!tskRanges.isEmpty()) {
carvedFileContainer.add(new CarvedFileContainer(fileName, fileSize, id, tskRanges));
carvedFiles.add(new CarvingResult.CarvedFile(fileName, fileSize, tskRanges));
}
}
return fileManager.addCarvedFiles(carvedFileContainer);
return fileManager.addCarvedFiles(new CarvingResult(af, carvedFiles));
} catch (NumberFormatException | TskCoreException ex) {
logger.log(Level.SEVERE, "Error parsing PhotoRec output and inserting it into the database: {0}", ex); //NON-NLS
}

View File

@ -18,48 +18,45 @@
*/
package org.sleuthkit.autopsy.modules.stix;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.logging.Level;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.JPanel;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import org.mitre.cybox.cybox_2.ObjectType;
import org.mitre.cybox.cybox_2.Observable;
import org.mitre.cybox.cybox_2.ObservableCompositionType;
import org.mitre.cybox.cybox_2.OperatorTypeEnum;
import org.mitre.cybox.objects.AccountObjectType;
import org.mitre.cybox.objects.Address;
import org.mitre.cybox.objects.DomainName;
import org.mitre.cybox.objects.EmailMessage;
import org.mitre.cybox.objects.FileObjectType;
import org.mitre.cybox.objects.SystemObjectType;
import org.mitre.cybox.objects.URIObjectType;
import org.mitre.cybox.objects.URLHistory;
import org.mitre.cybox.objects.WindowsNetworkShare;
import org.mitre.cybox.objects.WindowsRegistryKey;
import org.mitre.stix.common_1.IndicatorBaseType;
import org.mitre.stix.indicator_2.Indicator;
import org.mitre.stix.stix_1.STIXPackage;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
import org.sleuthkit.datamodel.TskCoreException;
import org.mitre.cybox.cybox_2.OperatorTypeEnum;
import org.mitre.cybox.objects.Address;
import org.mitre.cybox.objects.FileObjectType;
import org.mitre.cybox.objects.URIObjectType;
import org.mitre.cybox.objects.EmailMessage;
import org.mitre.cybox.objects.WindowsNetworkShare;
import org.mitre.cybox.objects.AccountObjectType;
import org.mitre.cybox.objects.SystemObjectType;
import org.mitre.cybox.objects.URLHistory;
import org.mitre.cybox.objects.DomainName;
import org.mitre.cybox.objects.WindowsRegistryKey;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
@ -180,6 +177,9 @@ public class STIXReportModule implements GeneralReportModule {
// Process each STIX file
for (File file : stixFiles) {
if (progressPanel.getStatus() == ReportStatus.CANCELED) {
return;
}
try {
processFile(file.getAbsolutePath(), progressPanel);
} catch (TskCoreException ex) {

File diff suppressed because it is too large Load Diff

0
Core/src/org/sleuthkit/autopsy/report/ReportKML.java Normal file → Executable file
View File

View File

@ -24,9 +24,7 @@ import java.util.ArrayList;
import java.util.Collections;
import static java.util.Collections.swap;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.JList;
import javax.swing.JPanel;
@ -138,7 +136,7 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener {
// Make sure that the report module has a valid non-null name.
private boolean moduleIsValid(ReportModule module) {
return module.getName() != null && !module.getName().isEmpty()
&& module.getRelativeFilePath() != null;
&& module.getRelativeFilePath() != null;
}
private void popupWarning(ReportModule module) {
@ -163,13 +161,12 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener {
*
* @return
*/
Map<TableReportModule, Boolean> getTableModuleStates() {
Map<TableReportModule, Boolean> reportModuleStates = new LinkedHashMap<>();
TableReportModule getTableModule() {
ReportModule mod = getSelectedModule();
if (tableModules.contains(mod)) {
reportModuleStates.put((TableReportModule) mod, Boolean.TRUE);
return (TableReportModule) mod;
}
return reportModuleStates;
return null;
}
/**
@ -177,13 +174,12 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener {
*
* @return
*/
Map<GeneralReportModule, Boolean> getGeneralModuleStates() {
Map<GeneralReportModule, Boolean> reportModuleStates = new LinkedHashMap<>();
GeneralReportModule getGeneralModule() {
ReportModule mod = getSelectedModule();
if (generalModules.contains(mod)) {
reportModuleStates.put((GeneralReportModule) mod, Boolean.TRUE);
return (GeneralReportModule) mod;
}
return reportModuleStates;
return null;
}
/**
@ -191,13 +187,12 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener {
*
* @return
*/
Map<FileReportModule, Boolean> getFileModuleStates() {
Map<FileReportModule, Boolean> reportModuleStates = new LinkedHashMap<>();
FileReportModule getFileModule() {
ReportModule mod = getSelectedModule();
if (fileModules.contains(mod)) {
reportModuleStates.put((FileReportModule) mod, Boolean.TRUE);
return (FileReportModule) mod;
}
return reportModuleStates;
return null;
}
/**

View File

@ -1,4 +1,4 @@
/*
/*
*
* Autopsy Forensic Browser
*
@ -65,13 +65,17 @@ public final class ReportWizardAction extends CallableSystemAction implements Pr
wiz.setTitle(NbBundle.getMessage(ReportWizardAction.class, "ReportWizardAction.reportWiz.title"));
if (DialogDisplayer.getDefault().notify(wiz) == WizardDescriptor.FINISH_OPTION) {
@SuppressWarnings("unchecked")
ReportGenerator generator = new ReportGenerator((Map<TableReportModule, Boolean>) wiz.getProperty("tableModuleStates"), //NON-NLS
(Map<GeneralReportModule, Boolean>) wiz.getProperty("generalModuleStates"), //NON-NLS
(Map<FileReportModule, Boolean>) wiz.getProperty("fileModuleStates")); //NON-NLS
generator.generateTableReports((Map<BlackboardArtifact.Type, Boolean>) wiz.getProperty("artifactStates"), (Map<String, Boolean>) wiz.getProperty("tagStates")); //NON-NLS
generator.generateFileListReports((Map<FileReportDataTypes, Boolean>) wiz.getProperty("fileReportOptions")); //NON-NLS
generator.generateGeneralReports();
generator.displayProgressPanels();
ReportGenerator generator = new ReportGenerator(); //NON-NLS
TableReportModule tableReport = (TableReportModule) wiz.getProperty("tableModule");
GeneralReportModule generalReport = (GeneralReportModule) wiz.getProperty("generalModule");
FileReportModule fileReport = (FileReportModule) wiz.getProperty("fileModule");
if (tableReport != null) {
generator.generateTableReport(tableReport, (Map<BlackboardArtifact.Type, Boolean>) wiz.getProperty("artifactStates"), (Map<String, Boolean>) wiz.getProperty("tagStates")); //NON-NLS
} else if (generalReport != null) {
generator.generateGeneralReport(generalReport);
} else if (fileReport != null) {
generator.generateFileListReport(fileReport, (Map<FileReportDataTypes, Boolean>) wiz.getProperty("fileReportOptions")); //NON-NLS
}
}
}

View File

@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.report;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.Map;
import java.util.prefs.Preferences;
import javax.swing.JButton;
import javax.swing.event.ChangeListener;
@ -108,17 +107,17 @@ class ReportWizardPanel1 implements WizardDescriptor.FinishablePanel<WizardDescr
@Override
public void storeSettings(WizardDescriptor wiz) {
Map<TableReportModule, Boolean> tables = getComponent().getTableModuleStates();
Map<GeneralReportModule, Boolean> generals = getComponent().getGeneralModuleStates();
wiz.putProperty("tableModuleStates", tables); //NON-NLS
wiz.putProperty("generalModuleStates", generals); //NON-NLS
wiz.putProperty("fileModuleStates", getComponent().getFileModuleStates()); //NON-NLS
TableReportModule module = getComponent().getTableModule();
GeneralReportModule general = getComponent().getGeneralModule();
wiz.putProperty("tableModule", module); //NON-NLS
wiz.putProperty("generalModule", general); //NON-NLS
wiz.putProperty("fileModule", getComponent().getFileModule()); //NON-NLS
// Store preferences that WizardIterator will use to determine what
// panels need to be shown
Preferences prefs = NbPreferences.forModule(ReportWizardPanel1.class);
prefs.putBoolean("tableModule", any(tables.values())); //NON-NLS
prefs.putBoolean("generalModule", any(generals.values())); //NON-NLS
prefs.putBoolean("tableModule", module != null); //NON-NLS
prefs.putBoolean("generalModule", general != null); //NON-NLS
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -18,8 +18,13 @@
*/
package org.sleuthkit.autopsy.timeline;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.logging.Level;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
@ -27,6 +32,7 @@ import org.openide.awt.ActionRegistration;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
import org.openide.util.actions.Presenter;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -36,8 +42,9 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline")
@ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false)
@ActionReferences(value = {
@ActionReference(path = "Menu/Tools", position = 100)})
public class OpenTimelineAction extends CallableSystemAction {
@ActionReference(path = "Menu/Tools", position = 100),
@ActionReference(path = "Toolbars/Case", position = 102)})
public class OpenTimelineAction extends CallableSystemAction implements Presenter.Toolbar {
private static final Logger LOGGER = Logger.getLogger(OpenTimelineAction.class.getName());
@ -45,10 +52,22 @@ public class OpenTimelineAction extends CallableSystemAction {
private static TimeLineController timeLineController = null;
private JButton toolbarButton = new JButton();
synchronized static void invalidateController() {
timeLineController = null;
}
public OpenTimelineAction() {
toolbarButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
performAction();
}
});
this.setEnabled(false);
}
@Override
public boolean isEnabled() {
/**
@ -102,4 +121,29 @@ public class OpenTimelineAction extends CallableSystemAction {
public boolean asynchronous() {
return false; // run on edt
}
/**
* Set this action to be enabled/disabled
*
* @param value whether to enable this action or not
*/
@Override
public void setEnabled(boolean value) {
super.setEnabled(value);
toolbarButton.setEnabled(value);
}
/**
* Returns the toolbar component of this action
*
* @return component the toolbar button
*/
@Override
public Component getToolbarPresenter() {
ImageIcon icon = new ImageIcon("Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_colorized_26.png"); //NON-NLS
toolbarButton.setIcon(icon);
toolbarButton.setText(this.getName());
return toolbarButton;
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,10 +19,9 @@
package org.sleuthkit.autopsy.timeline;
/**
*
* Enumeration of view modes.
*/
public enum ViewMode {
COUNTS,
DETAIL,
LIST;

View File

@ -597,7 +597,7 @@ public class EventsRepository {
timeMap.put(FileSystemTypes.FILE_MODIFIED, f.getMtime());
/*
* if there are no legitimate ( greater tan zero ) time stamps ( eg,
* if there are no legitimate ( greater than zero ) time stamps ( eg,
* logical/local files) skip the rest of the event generation: this
* should result in droping logical files, since they do not have
* legitimate time stamps.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,10 +18,8 @@
*/
package org.sleuthkit.autopsy.timeline.ui;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
@ -40,9 +38,14 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ViewMode;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
/**
* Base class for views that can be hosted in the ViewFrame
*
*/
public abstract class AbstractTimeLineView extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(AbstractTimeLineView.class.getName());
@ -60,12 +63,6 @@ public abstract class AbstractTimeLineView extends BorderPane {
*/
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false);
/**
* List of Nodes to insert into the toolbar. This should be set in an
* implementation's constructor.
*/
private List<Node> settingsNodes;
/**
* Listener that is attached to various properties that should trigger a
* view update when they change.
@ -136,8 +133,8 @@ public abstract class AbstractTimeLineView extends BorderPane {
/**
* Refresh this view based on current state of zoom / filters. Primarily
* this invokes the background ViewRefreshTask returned by
* getUpdateTask(), which derived classes must implement.
* this invokes the background ViewRefreshTask returned by getUpdateTask(),
* which derived classes must implement.
*
* TODO: replace this logic with a javafx Service ? -jm
*/
@ -186,26 +183,35 @@ public abstract class AbstractTimeLineView extends BorderPane {
protected abstract Task<Boolean> getNewUpdateTask();
/**
* Get a List of Nodes containing settings widgets to insert into this
* view's header.
* Get the ViewMode for this view.
*
* @return The ViewMode for this view.
*/
protected abstract ViewMode getViewMode();
/**
* Get a List of Nodes containing settings widgets to insert into top
* ToolBar of the ViewFrame.
*
* @return The List of settings Nodes.
*/
protected List<Node> getSettingsNodes() {
return Collections.unmodifiableList(settingsNodes);
}
abstract protected ImmutableList<Node> getSettingsControls();
/**
* Set the List of Nodes containing settings widgets to insert into this
* view's header.
* Does this view have custom time navigation controls that should replace
* the default ones from the ViewFrame?
*
*
* @param settingsNodes The List of Nodes containing settings widgets to
* insert into this view's header.
* @return True if this view have custom time navigation controls.
*/
final protected void setSettingsNodes(List<Node> settingsNodes) {
this.settingsNodes = new ArrayList<>(settingsNodes);
}
abstract protected boolean hasCustomTimeNavigationControls();
/**
* Get a List of Nodes containing controls to insert into the lower time
* range ToolBar of the ViewFrame.
*
* @return The List of Nodes.
*/
abstract protected ImmutableList<Node> getTimeNavigationControls();
/**
* Dispose of this view and any resources it holds onto.

View File

@ -35,12 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
Timeline.ui.ZoomRanges.all.text=All
ViewFrame.histogramTask.title=Rebuild Histogram
ViewFrame.histogramTask.preparing=preparing
ViewFrame.histogramTask.resetUI=resetting ui
ViewFrame.histogramTask.queryDb=querying db
ViewFrame.histogramTask.updateUI2=updating ui
ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.
ViewFrame.zoomButton.text=Zoom to events
TimeZonePanel.localRadio.text=Local Time Zone
TimeZonePanel.otherRadio.text=GMT / UTC

View File

@ -125,7 +125,7 @@
</children>
</StackPane>
<Separator />
<ToolBar>
<ToolBar fx:id="timeRangeToolBar">
<items>
<Label fx:id="startLabel" contentDisplay="RIGHT" minWidth="-Infinity">
<graphic>
@ -141,7 +141,7 @@
<Insets left="10.0" right="10.0" />
</HBox.margin>
</Separator>
<HBox>
<HBox fx:id="zoomInOutHBox">
<children>
<Button fx:id="zoomOutButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
<graphic>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-16 Basis Technology Corp.
* Copyright 2011-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,19 +18,24 @@
*/
package org.sleuthkit.autopsy.timeline.ui;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.Subscribe;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
@ -69,6 +74,7 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
@ -84,6 +90,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.DBUpdatedEvent;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
import static org.sleuthkit.autopsy.timeline.ui.Bundle.*;
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
@ -93,7 +100,8 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/**
* A container for an AbstractTimelineView. Has a Toolbar on top to hold
* settings widgets supplied by contained AbstractTimelineView, and the
* histogram / time selection on bottom.
* histogram / time selection on bottom. The time selection Toolbar has default
* controls that can be replaced by ones supplied by the current view.
*
* TODO: Refactor common code out of histogram and CountsView? -jm
*/
@ -101,14 +109,15 @@ final public class ViewFrame extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(ViewFrame.class.getName());
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
private static final Image WARNING = new Image("org/sleuthkit/autopsy/timeline/images/warning_triangle.png", 16, 16, true, true); // NON-NLS
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); //NON-NLS
private static final Image WARNING = new Image("org/sleuthkit/autopsy/timeline/images/warning_triangle.png", 16, 16, true, true); //NON-NLS
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); //NON-NLS
private static final Background GRAY_BACKGROUND = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
/**
* Region that will be stacked in between the no-events "dialog" and the
* hosted AbstractTimelineView in order to gray out the AbstractTimelineView.
* hosted AbstractTimelineView in order to gray out the
* AbstractTimelineView.
*/
private final static Region NO_EVENTS_BACKGROUND = new Region() {
{
@ -117,6 +126,18 @@ final public class ViewFrame extends BorderPane {
}
};
/**
* The scene graph Nodes for the current view's settings will be inserted
* into the toolbar at this index.
*/
private static final int SETTINGS_TOOLBAR_INSERTION_INDEX = 2;
/**
* The scene graph Nodes for the current view's time navigation controls
* will be inserted into the toolbar at this index.
*/
private static final int TIME_TOOLBAR_INSERTION_INDEX = 2;
@GuardedBy("this")
private LoggedTask<Void> histogramTask;
@ -139,6 +160,19 @@ final public class ViewFrame extends BorderPane {
private final RangeSlider rangeSlider = new RangeSlider(0, 1.0, .25, .75);
/**
* The lower tool bar that has controls to adjust the viewed timerange.
*/
@FXML
private ToolBar timeRangeToolBar;
/**
* Parent for the default zoom in/out buttons that can be replaced in some
* views(eg List View)
*/
@FXML
private HBox zoomInOutHBox;
//// time range selection components
@FXML
private MenuButton zoomMenuButton;
@ -158,6 +192,8 @@ final public class ViewFrame extends BorderPane {
//// header toolbar componenets
@FXML
private ToolBar toolBar;
private ToggleGroupValue<ViewMode> viewModeToggleGroup;
@FXML
private Label viewModeLabel;
@FXML
@ -168,6 +204,7 @@ final public class ViewFrame extends BorderPane {
private ToggleButton detailsToggle;
@FXML
private ToggleButton listToggle;
@FXML
private Button snapShotButton;
@FXML
@ -176,7 +213,27 @@ final public class ViewFrame extends BorderPane {
private Button updateDBButton;
/*
* Wraps contained AbstractTimelineView so that we can show notifications over it.
* Default zoom in/out buttons provided by the ViewFrame, some views replace
* these with other nodes (eg, list view)
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private ImmutableList<Node> defaultTimeNavigationNodes;
/*
* The settings nodes for the current view.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final ObservableList<Node> settingsNodes = FXCollections.observableArrayList();
/*
* The time nagivation nodes for the current view.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final ObservableList<Node> timeNavigationNodes = FXCollections.observableArrayList();
/**
* Wraps the contained AbstractTimelineView so that we can show
* notifications over it.
*/
private final NotificationPane notificationPane = new NotificationPane();
@ -252,7 +309,8 @@ final public class ViewFrame extends BorderPane {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
this.eventsTree = eventsTree;
FXMLConstructor.construct(this, "ViewFrame.fxml"); // NON-NLS
FXMLConstructor.construct(this, "ViewFrame.fxml"); //NON-NLS
}
@FXML
@ -267,12 +325,15 @@ final public class ViewFrame extends BorderPane {
"ViewFrame.tagsAddedOrDeleted=Tags have been created and/or deleted. The view may not be up to date."
})
void initialize() {
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert rangeHistogramStack != null : "fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
assert rangeHistogramStack != null : "fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; //NON-NLS
assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; //NON-NLS
defaultTimeNavigationNodes = ImmutableList.of(zoomInOutHBox, zoomMenuButton);
timeNavigationNodes.setAll(defaultTimeNavigationNodes);
//configure notification pane
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
@ -283,17 +344,14 @@ final public class ViewFrame extends BorderPane {
countsToggle.setText(Bundle.ViewFrame_countsToggle_text());
detailsToggle.setText(Bundle.ViewFrame_detailsToggle_text());
listToggle.setText(Bundle.ViewFrame_listToggle_text());
ToggleGroupValue<ViewMode> visModeToggleGroup = new ToggleGroupValue<>();
visModeToggleGroup.add(listToggle, ViewMode.LIST);
visModeToggleGroup.add(detailsToggle, ViewMode.DETAIL);
visModeToggleGroup.add(countsToggle, ViewMode.COUNTS);
modeSegButton.setToggleGroup(visModeToggleGroup);
visModeToggleGroup.valueProperty().addListener((observable, oldVisMode, newValue) -> {
controller.setViewMode(newValue != null ? newValue : (oldVisMode != null ? oldVisMode : ViewMode.COUNTS));
});
viewModeToggleGroup = new ToggleGroupValue<>();
viewModeToggleGroup.add(listToggle, ViewMode.LIST);
viewModeToggleGroup.add(detailsToggle, ViewMode.DETAIL);
viewModeToggleGroup.add(countsToggle, ViewMode.COUNTS);
modeSegButton.setToggleGroup(viewModeToggleGroup);
viewModeToggleGroup.valueProperty().addListener((observable, oldViewMode, newViewVode) ->
controller.setViewMode(newViewVode != null ? newViewVode : (oldViewMode != null ? oldViewMode : ViewMode.COUNTS))
);
controller.viewModeProperty().addListener(viewMode -> syncViewMode());
syncViewMode();
@ -330,7 +388,7 @@ final public class ViewFrame extends BorderPane {
* rangeslider track doesn't extend to edge of node,and so the
* histrogram doesn't quite line up with the rangeslider
*/
histogramBox.setStyle(" -fx-padding: 0,0.5em,0,.5em; "); // NON-NLS
histogramBox.setStyle(" -fx-padding: 0,0.5em,0,.5em; "); //NON-NLS
//configure zoom buttons
zoomMenuButton.getItems().clear();
@ -362,11 +420,10 @@ final public class ViewFrame extends BorderPane {
}
/**
* Handle TagsUpdatedEvents by marking that the view needs to be
* refreshed.
* Handle TagsUpdatedEvents by marking that the view needs to be refreshed.
*
* NOTE: This ViewFrame must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
* NOTE: This ViewFrame must be registered with the filteredEventsModel's
* EventBus in order for this handler to be invoked.
*
* @param event The TagsUpdatedEvent to handle.
*/
@ -385,8 +442,8 @@ final public class ViewFrame extends BorderPane {
* Handle a RefreshRequestedEvent from the events model by clearing the
* refresh notification.
*
* NOTE: This ViewFrame must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
* NOTE: This ViewFrame must be registered with the filteredEventsModel's
* EventBus in order for this handler to be invoked.
*
* @param event The RefreshRequestedEvent to handle.
*/
@ -400,11 +457,10 @@ final public class ViewFrame extends BorderPane {
}
/**
* Handle a DBUpdatedEvent from the events model by refreshing the
* view.
* Handle a DBUpdatedEvent from the events model by refreshing the view.
*
* NOTE: This ViewFrame must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
* NOTE: This ViewFrame must be registered with the filteredEventsModel's
* EventBus in order for this handler to be invoked.
*
* @param event The DBUpdatedEvent to handle.
*/
@ -419,8 +475,8 @@ final public class ViewFrame extends BorderPane {
* Handle a DataSourceAddedEvent from the events model by showing a
* notification.
*
* NOTE: This ViewFrame must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
* NOTE: This ViewFrame must be registered with the filteredEventsModel's
* EventBus in order for this handler to be invoked.
*
* @param event The DataSourceAddedEvent to handle.
*/
@ -439,8 +495,8 @@ final public class ViewFrame extends BorderPane {
* Handle a DataSourceAnalysisCompletedEvent from the events modelby showing
* a notification.
*
* NOTE: This ViewFrame must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
* NOTE: This ViewFrame must be registered with the filteredEventsModel's
* EventBus in order for this handler to be invoked.
*
* @param event The DataSourceAnalysisCompletedEvent to handle.
*/
@ -458,19 +514,23 @@ final public class ViewFrame extends BorderPane {
/**
* Refresh the Histogram to represent the current state of the DB.
*/
@NbBundle.Messages({"ViewFrame.histogramTask.title=Rebuilding Histogram",
"ViewFrame.histogramTask.preparing=Preparing",
"ViewFrame.histogramTask.resetUI=Resetting UI",
"ViewFrame.histogramTask.queryDb=Querying FB",
"ViewFrame.histogramTask.updateUI2=Updating UI"})
synchronized private void refreshHistorgram() {
if (histogramTask != null) {
histogramTask.cancel(true);
}
histogramTask = new LoggedTask<Void>(
NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.title"), true) { // NON-NLS
histogramTask = new LoggedTask<Void>(Bundle.ViewFrame_histogramTask_title(), true) {
private final Lighting lighting = new Lighting();
@Override
protected Void call() throws Exception {
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.preparing")); // NON-NLS
updateMessage(ViewFrame_histogramTask_preparing());
long max = 0;
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
@ -483,7 +543,7 @@ final public class ViewFrame extends BorderPane {
//clear old data, and reset ranges and series
Platform.runLater(() -> {
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.resetUI")); // NON-NLS
updateMessage(ViewFrame_histogramTask_resetUI());
});
@ -500,7 +560,7 @@ final public class ViewFrame extends BorderPane {
start = end;
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.queryDb")); // NON-NLS
updateMessage(ViewFrame_histogramTask_queryDb());
//query for current range
long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
bins.add(count);
@ -510,7 +570,7 @@ final public class ViewFrame extends BorderPane {
final double fMax = Math.log(max);
final ArrayList<Long> fbins = new ArrayList<>(bins);
Platform.runLater(() -> {
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.updateUI2")); // NON-NLS
updateMessage(ViewFrame_histogramTask_updateUI2());
histogramBox.getChildren().clear();
@ -543,7 +603,7 @@ final public class ViewFrame extends BorderPane {
}
/**
* Refresh the time selection UI to match the current zoome paramaters.
* Refresh the time selection UI to match the current zoom parameters.
*/
private void refreshTimeUI() {
RangeDivisionInfo rangeDivisionInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
@ -576,57 +636,52 @@ final public class ViewFrame extends BorderPane {
}
/**
* Switch to the given ViewMode, by swapping out the hosted
* AbstractTimelineView for one of the correct type.
* Sync up the view shown in the UI to the one currently active according to
* the controller. Swaps out the hosted AbstractTimelineView for a new one
* of the correct type.
*/
private void syncViewMode() {
AbstractTimeLineView view;
ViewMode viewMode = controller.viewModeProperty().get();
ViewMode newViewMode = controller.getViewMode();
//make new view.
switch (viewMode) {
case LIST:
view = new ListViewPane(controller);
Platform.runLater(() -> {
listToggle.setSelected(true);
//TODO: should remove listeners from events tree
});
break;
case COUNTS:
view = new CountsViewPane(controller);
Platform.runLater(() -> {
countsToggle.setSelected(true);
//TODO: should remove listeners from events tree
});
break;
case DETAIL:
DetailViewPane detailViewPane = new DetailViewPane(controller);
Platform.runLater(() -> {
detailsToggle.setSelected(true);
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
eventsTree.setDetailViewPane(detailViewPane);
});
view = detailViewPane;
break;
default:
throw new IllegalArgumentException("Unknown ViewMode: " + viewMode.toString());
}
//Set the new AbstractTimeLineView as the one hosted by this ViewFrame.
Platform.runLater(() -> {
//clear out old view.
if (hostedView != null) {
toolBar.getItems().removeAll(hostedView.getSettingsNodes());
hostedView.dispose();
}
hostedView = view;
//setup new view.
//Set a new AbstractTimeLineView as the one hosted by this ViewFrame.
switch (newViewMode) {
case LIST:
hostedView = new ListViewPane(controller);
//TODO: should remove listeners from events tree
break;
case COUNTS:
hostedView = new CountsViewPane(controller);
//TODO: should remove listeners from events tree
break;
case DETAIL:
DetailViewPane detailViewPane = new DetailViewPane(controller);
//link events tree to detailview instance.
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
eventsTree.setDetailViewPane(detailViewPane);
hostedView = detailViewPane;
break;
default:
throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS
}
viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically
//configure settings and time navigation nodes
setViewSettingsControls(hostedView.getSettingsControls());
setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls()
? hostedView.getTimeNavigationControls()
: defaultTimeNavigationNodes);
//do further setup of new view.
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
hostedView.refresh();
toolBar.getItems().addAll(2, view.getSettingsNodes());
notificationPane.setContent(hostedView);
//listen to has events property and show "dialog" if it is false.
hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
notificationPane.setContent(hostedView.hasVisibleEvents()
@ -640,6 +695,32 @@ final public class ViewFrame extends BorderPane {
});
}
/**
* Show the given List of Nodes in the top ToolBar. Replaces any settings
* Nodes that may have previously been set with the given List of Nodes.
*
* @param newSettingsNodes The Nodes to show in the ToolBar.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void setViewSettingsControls(List<Node> newSettingsNodes) {
toolBar.getItems().removeAll(this.settingsNodes); //remove old nodes
this.settingsNodes.setAll(newSettingsNodes);
toolBar.getItems().addAll(SETTINGS_TOOLBAR_INSERTION_INDEX, settingsNodes);
}
/**
* Show the given List of Nodes in the time range ToolBar. Replaces any
* Nodes that may have previously been set with the given List of Nodes.
*
* @param newSettingsNodes The Nodes to show in the time range ToolBar.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void setTimeNavigationControls(List<Node> timeNavigationNodes) {
timeRangeToolBar.getItems().removeAll(this.timeNavigationNodes); //remove old nodes
this.timeNavigationNodes.setAll(timeNavigationNodes);
timeRangeToolBar.getItems().addAll(TIME_TOOLBAR_INSERTION_INDEX, timeNavigationNodes);
}
@NbBundle.Messages("NoEventsDialog.titledPane.text=No Visible Events")
private class NoEventsDialog extends StackPane {
@ -660,17 +741,18 @@ final public class ViewFrame extends BorderPane {
private NoEventsDialog(Runnable closeCallback) {
this.closeCallback = closeCallback;
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); //NON-NLS
}
@FXML
@NbBundle.Messages("ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.")
void initialize() {
assert resetFiltersButton != null : "fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
assert resetFiltersButton != null : "fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "ViewFrame.noEventsDialogLabel.text")); // NON-NLS
noEventsDialogLabel.setText(Bundle.ViewFrame_noEventsDialogLabel_text());
dismissButton.setOnAction(actionEvent -> closeCallback.run());

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-16 Basis Technology Corp.
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.timeline.ui.countsview;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Map;
@ -56,6 +57,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ViewMode;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
@ -113,13 +115,10 @@ public class CountsViewPane extends AbstractTimelineChart<String, Number, Node,
public CountsViewPane(TimeLineController controller) {
super(controller);
setChart(new EventCountsChart(controller, dateAxis, countAxis, getSelectedNodes()));
getChart().setData(dataSeries);
Tooltip.install(getChart(), getDefaultTooltip());
setSettingsNodes(new CountsViewSettingsPane().getChildrenUnmodifiable());
dateAxis.getTickMarks().addListener((Observable tickMarks) -> layoutDateLabels());
dateAxis.categorySpacingProperty().addListener((Observable spacing) -> layoutDateLabels());
dateAxis.getCategories().addListener((Observable categories) -> layoutDateLabels());
@ -167,6 +166,26 @@ public class CountsViewPane extends AbstractTimelineChart<String, Number, Node,
createSeries();
}
@Override
final protected ViewMode getViewMode() {
return ViewMode.COUNTS;
}
@Override
protected ImmutableList<Node> getSettingsControls() {
return ImmutableList.copyOf(new CountsViewSettingsPane().getChildrenUnmodifiable());
}
@Override
protected boolean hasCustomTimeNavigationControls() {
return false;
}
@Override
protected ImmutableList<Node> getTimeNavigationControls() {
return ImmutableList.of();
}
/**
* Set the appropriate label on the vertical axis, depending on the selected
* scale.

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-16 Basis Technology Corp.
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.timeline.ui.detailview;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
@ -29,6 +30,7 @@ import javafx.beans.Observable;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
@ -51,6 +53,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ViewMode;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
@ -112,7 +115,6 @@ public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe,
//initialize chart;
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());
@ -243,6 +245,26 @@ public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe,
return 0;
}
@Override
final protected ViewMode getViewMode() {
return ViewMode.DETAIL;
}
@Override
protected ImmutableList<Node> getSettingsControls() {
return ImmutableList.copyOf(new DetailViewSettingsPane(getChart().getLayoutSettings()).getChildrenUnmodifiable());
}
@Override
protected boolean hasCustomTimeNavigationControls() {
return false;
}
@Override
protected ImmutableList<Node> getTimeNavigationControls() {
return ImmutableList.of();
}
/**
* A Pane that contains widgets to adjust settings specific to a
* DetailViewPane
@ -286,7 +308,7 @@ public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe,
DetailViewSettingsPane(DetailsChartLayoutSettings layoutSettings) {
this.layoutSettings = layoutSettings;
FXMLConstructor.construct(DetailViewSettingsPane.this, "DetailViewSettingsPane.fxml"); // NON-NLS
FXMLConstructor.construct(DetailViewSettingsPane.this, "DetailViewSettingsPane.fxml"); //NON-NLS
}
@NbBundle.Messages({
@ -300,11 +322,11 @@ public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe,
"DetailViewPane.hiddenRadio.text=Hide Description"})
@FXML
void initialize() {
assert bandByTypeBox != null : "fx:id=\"bandByTypeBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
assert oneEventPerRowBox != null : "fx:id=\"oneEventPerRowBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
assert truncateAllBox != null : "fx:id=\"truncateAllBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
assert truncateWidthSlider != null : "fx:id=\"truncateAllSlider\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
assert pinnedEventsToggle != null : "fx:id=\"pinnedEventsToggle\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
assert bandByTypeBox != null : "fx:id=\"bandByTypeBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
assert oneEventPerRowBox != null : "fx:id=\"oneEventPerRowBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
assert truncateAllBox != null : "fx:id=\"truncateAllBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
assert truncateWidthSlider != null : "fx:id=\"truncateAllSlider\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
assert pinnedEventsToggle != null : "fx:id=\"pinnedEventsToggle\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
//bind widgets to settings object properties
bandByTypeBox.selectedProperty().bindBidirectional(layoutSettings.bandByTypeProperty());

View File

@ -1,19 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Cursor?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox alignment="CENTER" BorderPane.alignment="CENTER">
<HBox alignment="CENTER_RIGHT" spacing="5.0" BorderPane.alignment="CENTER">
<children>
<Region HBox.hgrow="ALWAYS" />
<Label fx:id="eventCountLabel" text=" # of events" />
<HBox fx:id="navControls" alignment="CENTER_LEFT" minHeight="38.0" spacing="5.0">
<children>
<Label text="Jump By: " />
<ComboBox fx:id="scrollInrementComboBox" prefHeight="32.0">
<HBox.margin>
<Insets right="10.0" />
</HBox.margin></ComboBox>
<Button fx:id="firstButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="32.0" prefWidth="32.0">
<graphic>
<ImageView>
<image>
<Image url="@../../images/resultset_first.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="previousButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="32.0" prefWidth="32.0">
<graphic>
<ImageView>
<image>
<Image url="@../../images/resultset_previous.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="nextButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="32.0" prefWidth="32.0">
<cursor>
<Cursor fx:constant="HAND" />
</cursor>
<graphic>
<ImageView>
<image>
<Image url="@../../images/resultset_next.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="lastButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="32.0" prefWidth="32.0">
<graphic>
<ImageView>
<image>
<Image url="@../../images/resultset_last.png" />
</image>
</ImageView>
</graphic>
</Button>
</children>
</HBox>
</children>
<BorderPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />

View File

@ -19,20 +19,30 @@
package org.sleuthkit.autopsy.timeline.ui.listvew;
import com.google.common.collect.Iterables;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@ -40,8 +50,12 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.MenuItem;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.SelectionMode;
@ -54,11 +68,14 @@ import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import javax.swing.Action;
import javax.swing.JMenuItem;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.Notifications;
import org.controlsfx.control.action.ActionUtils;
import org.openide.awt.Actions;
import org.openide.util.NbBundle;
import org.openide.util.actions.Presenter;
@ -88,11 +105,40 @@ class ListTimeline extends BorderPane {
private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); //NON-NLS
private static final Image FIRST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_first.png"); //NON-NLS
private static final Image PREVIOUS = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_previous.png"); //NON-NLS
private static final Image NEXT = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_next.png"); //NON-NLS
private static final Image LAST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_last.png"); //NON-NLS
/**
* call-back used to wrap the CombinedEvent in a ObservableValue
*/
private static final Callback<TableColumn.CellDataFeatures<CombinedEvent, CombinedEvent>, ObservableValue<CombinedEvent>> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue());
private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
ChronoField.YEAR,
ChronoField.MONTH_OF_YEAR,
ChronoField.DAY_OF_MONTH,
ChronoField.HOUR_OF_DAY,
ChronoField.MINUTE_OF_HOUR,
ChronoField.SECOND_OF_MINUTE);
@FXML
private HBox navControls;
@FXML
private ComboBox<ChronoField> scrollInrementComboBox;
@FXML
private Button firstButton;
@FXML
private Button previousButton;
@FXML
private Button nextButton;
@FXML
private Button lastButton;
@FXML
private Label eventCountLabel;
@ -118,6 +164,8 @@ class ListTimeline extends BorderPane {
*/
private final ObservableList<Long> selectedEventIDs = FXCollections.observableArrayList();
private final ConcurrentSkipListSet<CombinedEvent> visibleEvents;
private final TimeLineController controller;
private final SleuthkitCase sleuthkitCase;
private final TagsManager tagsManager;
@ -128,10 +176,12 @@ class ListTimeline extends BorderPane {
* @param controller The controller for this timeline
*/
ListTimeline(TimeLineController controller) {
this.controller = controller;
sleuthkitCase = controller.getAutopsyCase().getSleuthkitCase();
tagsManager = controller.getAutopsyCase().getServices().getTagsManager();
FXMLConstructor.construct(this, ListTimeline.class, "ListTimeline.fxml"); //NON-NLS
this.visibleEvents = new ConcurrentSkipListSet<>(Comparator.comparing(table.getItems()::indexOf));
}
@FXML
@ -147,6 +197,16 @@ class ListTimeline extends BorderPane {
assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
assert knownColumn != null : "fx:id=\"knownColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
scrollInrementComboBox.setButtonCell(new ChronoFieldListCell());
scrollInrementComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
scrollInrementComboBox.getItems().setAll(SCROLL_BY_UNITS);
scrollInrementComboBox.getSelectionModel().select(ChronoField.YEAR);
ActionUtils.configureButton(new ScrollToFirst(), firstButton);
ActionUtils.configureButton(new ScrollToPrevious(), previousButton);
ActionUtils.configureButton(new ScrollToNext(), nextButton);
ActionUtils.configureButton(new ScrollToLast(), lastButton);
//override default row with one that provides context menus
table.setRowFactory(tableView -> new EventRow());
@ -248,6 +308,18 @@ class ListTimeline extends BorderPane {
table.requestFocus();
}
List<Node> getNavControls() {
return Collections.singletonList(navControls);
}
private void scrollToAndFocus(Integer index) {
table.requestFocus();
if (visibleEvents.contains(table.getItems().get(index)) == false) {
table.scrollTo(index);
}
table.getFocusModel().focus(index);
}
/**
* TableCell to show the (sub) type of an event.
*/
@ -514,11 +586,16 @@ class ListTimeline extends BorderPane {
"ListChart.errorMsg=There was a problem getting the content for the selected event."})
@Override
protected void updateItem(CombinedEvent item, boolean empty) {
CombinedEvent oldItem = getItem();
if (oldItem != null) {
visibleEvents.remove(oldItem);
}
super.updateItem(item, empty);
if (empty || item == null) {
event = null;
} else {
visibleEvents.add(item);
event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
setOnContextMenuRequested(contextMenuEvent -> {
@ -572,4 +649,122 @@ class ListTimeline extends BorderPane {
}
}
}
private class ChronoFieldListCell extends ListCell<ChronoField> {
@Override
protected void updateItem(ChronoField item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
String displayName = item.getDisplayName(Locale.getDefault());
setText(String.join(" ", StringUtils.splitByCharacterTypeCamelCase(displayName)));
}
}
}
private class ScrollToFirst extends org.controlsfx.control.action.Action {
ScrollToFirst() {
super("", actionEvent -> scrollToAndFocus(0));
setGraphic(new ImageView(FIRST));
disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
}
}
private class ScrollToLast extends org.controlsfx.control.action.Action {
ScrollToLast() {
super("", actionEvent -> scrollToAndFocus(table.getItems().size() - 1));
setGraphic(new ImageView(LAST));
IntegerBinding size = Bindings.size(table.getItems());
disabledProperty().bind(size.isEqualTo(0).or(
table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
}
}
private class ScrollToNext extends org.controlsfx.control.action.Action {
ScrollToNext() {
super("", actionEvent -> {
ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
ZoneId timeZoneID = TimeLineController.getTimeZoneID();
TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
int focusedIndex = table.getFocusModel().getFocusedIndex();
CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
if (-1 == focusedIndex || null == focusedItem) {
focusedItem = visibleEvents.first();
focusedIndex = table.getItems().indexOf(focusedItem);
}
ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
ZonedDateTime nextDateTime = focusedDateTime.plus(1, selectedUnit);//
for (ChronoField field : SCROLL_BY_UNITS) {
if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
nextDateTime = nextDateTime.with(field, field.rangeRefinedBy(nextDateTime).getMinimum());//
}
}
long nextMillis = nextDateTime.toInstant().toEpochMilli();
int nextIndex = table.getItems().size() - 1;
for (int i = focusedIndex; i < table.getItems().size(); i++) {
if (table.getItems().get(i).getStartMillis() >= nextMillis) {
nextIndex = i;
break;
}
}
scrollToAndFocus(nextIndex);
});
setGraphic(new ImageView(NEXT));
IntegerBinding size = Bindings.size(table.getItems());
disabledProperty().bind(size.isEqualTo(0).or(
table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
}
}
private class ScrollToPrevious extends org.controlsfx.control.action.Action {
ScrollToPrevious() {
super("", actionEvent -> {
ZoneId timeZoneID = TimeLineController.getTimeZoneID();
ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
int focusedIndex = table.getFocusModel().getFocusedIndex();
CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
if (-1 == focusedIndex || null == focusedItem) {
focusedItem = visibleEvents.last();
focusedIndex = table.getItems().indexOf(focusedItem);
}
ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
ZonedDateTime previousDateTime = focusedDateTime.minus(1, selectedUnit);//
for (ChronoField field : SCROLL_BY_UNITS) {
if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
previousDateTime = previousDateTime.with(field, field.rangeRefinedBy(previousDateTime).getMaximum());//
}
}
long previousMillis = previousDateTime.toInstant().toEpochMilli();
int previousIndex = 0;
for (int i = focusedIndex; i > 0; i--) {
if (table.getItems().get(i).getStartMillis() <= previousMillis) {
previousIndex = i;
break;
}
}
scrollToAndFocus(previousIndex);
});
setGraphic(new ImageView(PREVIOUS));
disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
}
}
}

View File

@ -18,14 +18,17 @@
*/
package org.sleuthkit.autopsy.timeline.ui.listvew;
import com.google.common.collect.ImmutableList;
import java.util.HashSet;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.concurrent.Task;
import javafx.scene.Parent;
import javafx.scene.Node;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ViewMode;
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
@ -48,7 +51,6 @@ public class ListViewPane extends AbstractTimeLineView {
//initialize chart;
setCenter(listTimeline);
setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable());
//keep controller's list of selected event IDs in sync with this list's
listTimeline.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
@ -66,13 +68,34 @@ public class ListViewPane extends AbstractTimeLineView {
listTimeline.clear();
}
private static class ListViewSettingsPane extends Parent {
@Override
final protected ViewMode getViewMode() {
return ViewMode.LIST;
}
@Override
protected ImmutableList<Node> getSettingsControls() {
return ImmutableList.of();
}
@Override
protected ImmutableList<Node> getTimeNavigationControls() {
return ImmutableList.copyOf(listTimeline.getNavControls());
}
@Override
protected boolean hasCustomTimeNavigationControls() {
return true;
}
private class ListUpdateTask extends ViewRefreshTask<Interval> {
@NbBundle.Messages({
"ListViewPane.loggedTask.queryDb=Retreiving event data",
"ListViewPane.loggedTask.name=Updating List View",
"ListViewPane.loggedTask.updateUI=Populating view"})
ListUpdateTask() {
super("List update task", true);
super(Bundle.ListViewPane_loggedTask_name(), true);
}
@Override
@ -91,10 +114,10 @@ public class ListViewPane extends AbstractTimeLineView {
resetView(eventsModel.getTimeRange());
//get the combined events to be displayed
updateMessage("Querying DB for events");
updateMessage(Bundle.ListViewPane_loggedTask_queryDb());
List<CombinedEvent> combinedEvents = eventsModel.getCombinedEvents();
updateMessage("Updating UI");
updateMessage(Bundle.ListViewPane_loggedTask_updateUI());
Platform.runLater(() -> {
//put the combined events into the table.
listTimeline.setCombinedEvents(combinedEvents);

View File

@ -31,7 +31,7 @@ import org.openide.util.Lookup;
*/
@OptionsPanelController.TopLevelRegistration(
categoryName = "#OptionsCategory_Name_Options",
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png",
iconBase = "org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_32.png",
keywords = "#OptionsCategory_Keywords_Options",
keywordsCategory = "Options",
position = 10

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB