From 5d590f12152dc6edfba037df376a6148c6510129 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 8 Feb 2018 17:51:38 -0500 Subject: [PATCH 1/4] 3457: SQLiteViewer should handle databases with WAL files --- .../autopsy/contentviewers/SQLiteViewer.java | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 627d30e87c..be763622fb 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -40,11 +40,16 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JComboBox; import javax.swing.SwingWorker; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @@ -64,6 +69,11 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { SQLiteTableView selectedTableView = new SQLiteTableView(); private SwingWorker worker; + + + //private final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + //private final Services services = new Services(sleuthkitCase); + //private final FileManager fileManager = services.getFileManager(); /** * Creates new form SQLiteViewer @@ -308,21 +318,25 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private void processSQLiteFile(AbstractFile sqliteFile) { tablesDropdownList.removeAllItems(); - + new SwingWorker() { @Override protected Boolean doInBackground() throws Exception { try { // Copy the file to temp folder - tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName() + "-" + sqliteFile.getId(); + tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName(); tmpDBFile = new File(tmpDBPathName); ContentUtils.writeToFile(sqliteFile, tmpDBFile); + // look for any meta files associated with this DB - WAL, SHM + findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-wal"); + findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-shm"); + // Open copy using JDBC Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS - + // Read all table names and schema return getTables(); } catch (IOException ex) { @@ -357,6 +371,45 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } + /** + * Searches for a meta file associated with the give SQLite db + * If found, copies the file to the temp folder + * + * @param sqliteFile - SQLIte db file being processed + * @param metaFileName name of meta file to look for + * + * @return true if the meta file is found and copied successfully, false otherwise + */ + private boolean findAndCopySQLiteMetaFile(AbstractFile sqliteFile, String metaFileName ) { + + SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + Services services = new Services(sleuthkitCase); + FileManager fileManager = services.getFileManager(); + + List metaFiles = null; + try { + metaFiles = fileManager.findFiles(sqliteFile.getDataSource(), metaFileName, sqliteFile.getParent().getName() ); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while searching SQLite meta file = " + metaFileName , ex); //NON-NLS + return false; + } + + if (metaFiles != null) { + for (AbstractFile metaFile: metaFiles) { + String tmpMetafilePathName = Case.getCurrentCase().getTempDirectory() + File.separator + metaFile.getName(); + + File tmpMetafile = new File(tmpMetafilePathName); + try { + ContentUtils.writeToFile(metaFile, tmpMetafile); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while copying SQLite meta file = " + metaFileName , ex); //NON-NLS + return false; + } + } + } + + return true; + } /** * Gets the table names and their schema from loaded SQLite db file * From ac46da9fb2fe7c33d91c1fa0acd581e1e6314d39 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 15 Feb 2018 06:01:06 -0500 Subject: [PATCH 2/4] Cleanup - removed commented out code, added class header. --- .../autopsy/contentviewers/SQLiteViewer.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index be763622fb..49d6231435 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -40,7 +40,6 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JComboBox; import javax.swing.SwingWorker; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -51,6 +50,10 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +/** + * A file content viewer for SQLITE db files. + * + */ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; @@ -67,13 +70,8 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private int currPage = 0; // curr page of rows being displayed SQLiteTableView selectedTableView = new SQLiteTableView(); - private SwingWorker worker; - - - //private final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); - //private final Services services = new Services(sleuthkitCase); - //private final FileManager fileManager = services.getFileManager(); + /** * Creates new form SQLiteViewer From 2b7494f4be9aea7176ee2b4df498680da8649ef6 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 16 Feb 2018 10:21:33 -0500 Subject: [PATCH 3/4] 3504: allow user to parse SQLite column as EPOCH date. Initial cut --- .../contentviewers/EpochTimeCellRenderer.java | 80 +++++++++++++++++++ .../contentviewers/SQLiteTableRowFactory.java | 34 +++++++- .../contentviewers/SQLiteTableView.java | 37 ++++++++- 3 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java new file mode 100644 index 0000000000..057ab30d14 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java @@ -0,0 +1,80 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.contentviewers; + +import java.awt.Component; +import java.awt.Font; +import java.lang.reflect.InvocationTargetException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; +import javax.swing.JTable; +import javax.swing.table.DefaultTableCellRenderer; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * + * @author raman + */ +public class EpochTimeCellRenderer extends DefaultTableCellRenderer { + + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); + + private static final String FORMAT_STRING = "yyyy/MM/dd HH:mm:ss"; //NON-NLS + private static final SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT_STRING); + + private final boolean renderAsEpoch; + + public EpochTimeCellRenderer(boolean renderAsEpoch) { + this.renderAsEpoch = renderAsEpoch; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value != null) { + + String textStr = "No Value"; + try { + if (value instanceof Node.Property) { + Node.Property np = (Node.Property)value; + textStr = np.getValue().toString(); + } + if (renderAsEpoch) { + long epochTime = Long.parseUnsignedLong(textStr); + if (epochTime > 0 ) { + + Font f = getFont(); + setFont(f.deriveFont(f.getStyle() | Font.ITALIC)); + setText(dateFormat.format(new Date(epochTime))); + } + else { + setText(textStr); + } + } + else { + setText(textStr); + } + + } + catch (NumberFormatException e) { + setText(textStr); + LOGGER.log(Level.INFO, "Error converting column value to number.", e); //NON-NLS + } catch (IllegalAccessException | InvocationTargetException ex) { + setText(""); + LOGGER.log(Level.SEVERE, "Error in getting column value.", ex); //NON-NLS + } + } + else { + setText(""); + } + + return this; + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java index 633f40260c..12ef2d09f1 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java @@ -18,9 +18,17 @@ */ package org.sleuthkit.autopsy.contentviewers; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import org.netbeans.swing.outline.Outline; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -31,9 +39,12 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; public class SQLiteTableRowFactory extends ChildFactory { private final List> rows; + //private final Outline outline; + private final List colActions; - public SQLiteTableRowFactory(List> rows) { + public SQLiteTableRowFactory(List> rows, List actions ) { this.rows = rows; + this.colActions = actions; } @Override @@ -52,7 +63,7 @@ public class SQLiteTableRowFactory extends ChildFactory { return null; } - return new SQLiteTableRowNode(rows.get(key)); + return new SQLiteTableRowNode(rows.get(key), this.colActions ); } } @@ -60,10 +71,14 @@ public class SQLiteTableRowFactory extends ChildFactory { class SQLiteTableRowNode extends AbstractNode { private final Map row; + //private final Outline outline; + private final List nodeActions; + - SQLiteTableRowNode(Map row) { + SQLiteTableRowNode(Map row, List actions) { super(Children.LEAF); this.row = row; + this.nodeActions = actions; } @Override @@ -85,4 +100,17 @@ class SQLiteTableRowNode extends AbstractNode { return s; } + + @Override + public Action[] getActions(boolean context) { + List actions = new ArrayList<>(); + + actions.addAll(Arrays.asList(super.getActions(context))); + + actions.addAll(nodeActions); + //actions.add(parseColAction); + + return actions.toArray(new Action[actions.size()]); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index 7ca873e13c..fa2c9991e8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -20,15 +20,20 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.BorderLayout; import java.awt.Component; +import java.awt.event.ActionEvent; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.SwingWorker; import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumnModel; @@ -103,7 +108,13 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { @Override protected Boolean doInBackground() throws Exception { - explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true))); + List nodeActions = new ArrayList<>(); + + nodeActions.add(new ParseColAction("Parse column as Epoch time", outline, new EpochTimeCellRenderer(true)) ); + //nodeActions.add(new ParseColAction("Display column as original type", outline, new DefaultTableCellRenderer()) ); + nodeActions.add(new ParseColAction("Display column as original type", outline, new EpochTimeCellRenderer(false)) ); + + explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows, nodeActions), true))); return false; } @@ -160,4 +171,28 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables + + +private class ParseColAction extends AbstractAction { + private final Outline outline; + private TableCellRenderer colCellRenderer; + + + private ParseColAction(String displayName, Outline outline, TableCellRenderer colCellRenderer ) { + super(displayName); + this.outline = outline; + this.colCellRenderer = colCellRenderer; + } + + @Override + public void actionPerformed(ActionEvent e) { + + int selCol = outline.getSelectedColumn(); + + TableColumnModel columnModel = outline.getColumnModel(); + + TableColumn column = columnModel.getColumn(selCol); + column.setCellRenderer(colCellRenderer); + } + } } From 707ec08effa2856d89d54c3a788200c184c4ca0c Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 21 Feb 2018 13:53:46 -0500 Subject: [PATCH 4/4] 3504: Allow user to format SQLite columns as EPOCH dates --- .../contentviewers/EpochTimeCellRenderer.java | 65 +++++++++---- .../contentviewers/SQLiteTableRowFactory.java | 30 +++--- .../contentviewers/SQLiteTableView.java | 91 ++++++++++++++++--- 3 files changed, 134 insertions(+), 52 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java index 057ab30d14..723c1ef0f3 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.contentviewers; @@ -17,8 +30,8 @@ import org.openide.nodes.Node; import org.sleuthkit.autopsy.coreutils.Logger; /** + * Custom Cell renderer to display a SQLite column cell as readable Epoch date/time * - * @author raman */ public class EpochTimeCellRenderer extends DefaultTableCellRenderer { @@ -26,40 +39,52 @@ public class EpochTimeCellRenderer extends DefaultTableCellRenderer { private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); private static final String FORMAT_STRING = "yyyy/MM/dd HH:mm:ss"; //NON-NLS - private static final SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT_STRING); + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(FORMAT_STRING); private final boolean renderAsEpoch; - public EpochTimeCellRenderer(boolean renderAsEpoch) { + EpochTimeCellRenderer(boolean renderAsEpoch) { this.renderAsEpoch = renderAsEpoch; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (value != null) { - - String textStr = "No Value"; + + // Set the forceground/background so its obvious when the cell is selected. + if (isSelected) { + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } else { + super.setForeground( table.getForeground()); + super.setBackground( table.getBackground()); + } + + if (value == null) { + setText(""); + } + else { + String textStr = ""; try { + // get the col property value if (value instanceof Node.Property) { - Node.Property np = (Node.Property)value; - textStr = np.getValue().toString(); + Node.Property nodeProp = (Node.Property)value; + textStr = nodeProp.getValue().toString(); } + if (renderAsEpoch) { long epochTime = Long.parseUnsignedLong(textStr); if (epochTime > 0 ) { - - Font f = getFont(); - setFont(f.deriveFont(f.getStyle() | Font.ITALIC)); - setText(dateFormat.format(new Date(epochTime))); + Font font = getFont(); + setFont(font.deriveFont(font.getStyle() | Font.ITALIC)); + setText(DATE_FORMAT.format(new Date(epochTime))); } else { setText(textStr); } } - else { + else { // Display raw data setText(textStr); } - } catch (NumberFormatException e) { setText(textStr); @@ -69,12 +94,12 @@ public class EpochTimeCellRenderer extends DefaultTableCellRenderer { LOGGER.log(Level.SEVERE, "Error in getting column value.", ex); //NON-NLS } } - else { - setText(""); - } return this; } + boolean isRenderingAsEpoch() { + return this.renderAsEpoch; + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java index 12ef2d09f1..02a08f7037 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java @@ -18,17 +18,12 @@ */ package org.sleuthkit.autopsy.contentviewers; -import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; -import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; -import org.netbeans.swing.outline.Outline; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -36,13 +31,15 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.sleuthkit.autopsy.datamodel.NodeProperty; +/** + * Factory class to generate nodes for SQLite table rows + */ public class SQLiteTableRowFactory extends ChildFactory { private final List> rows; - //private final Outline outline; private final List colActions; - public SQLiteTableRowFactory(List> rows, List actions ) { + SQLiteTableRowFactory(List> rows, List actions ) { this.rows = rows; this.colActions = actions; } @@ -68,13 +65,15 @@ public class SQLiteTableRowFactory extends ChildFactory { } +/** + * + * Node for SQLite table row + */ class SQLiteTableRowNode extends AbstractNode { private final Map row; - //private final Outline outline; private final List nodeActions; - SQLiteTableRowNode(Map row, List actions) { super(Children.LEAF); this.row = row; @@ -84,21 +83,20 @@ class SQLiteTableRowNode extends AbstractNode { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set properties = s.get(Sheet.PROPERTIES); + Sheet sheet = super.createSheet(); + Sheet.Set properties = sheet.get(Sheet.PROPERTIES); if (properties == null) { properties = Sheet.createPropertiesSet(); - s.put(properties); + sheet.put(properties); } for (Map.Entry col : row.entrySet()) { String colName = col.getKey(); String colVal = col.getValue().toString(); - properties.put(new NodeProperty<>(colName, colName, colName, colVal)); // NON-NLS } - return s; + return sheet; } @Override @@ -106,10 +104,8 @@ class SQLiteTableRowNode extends AbstractNode { List actions = new ArrayList<>(); actions.addAll(Arrays.asList(super.getActions(context))); - actions.addAll(nodeActions); - //actions.add(parseColAction); - + return actions.toArray(new Action[actions.size()]); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index fa2c9991e8..77c0814c55 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -27,6 +27,8 @@ import java.util.Map; import java.util.Objects; import javax.swing.AbstractAction; import javax.swing.Action; +import javax.swing.JMenu; +import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.ListSelectionModel; @@ -41,7 +43,12 @@ import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; +import org.openide.util.NbBundle; +import org.openide.util.actions.Presenter; +/** + * Panel to display a SQLite table + */ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { private final org.openide.explorer.view.OutlineView outlineView; @@ -68,6 +75,7 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { outline.setRowSelectionAllowed(false); outline.setRootVisible(false); + outline.setCellSelectionEnabled(true); explorerManager = new ExplorerManager(); } @@ -76,6 +84,10 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { * * @param tableRows */ + @NbBundle.Messages({"SQLiteTableView.DisplayAs.text=Display as", + "SQLiteTableView.DisplayAsMenuItem.Date=Date", + "SQLiteTableView.DisplayAsMenuItem.RawData=Raw Data" + }) void setupTable(List> tableRows) { @@ -110,10 +122,8 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { List nodeActions = new ArrayList<>(); - nodeActions.add(new ParseColAction("Parse column as Epoch time", outline, new EpochTimeCellRenderer(true)) ); - //nodeActions.add(new ParseColAction("Display column as original type", outline, new DefaultTableCellRenderer()) ); - nodeActions.add(new ParseColAction("Display column as original type", outline, new EpochTimeCellRenderer(false)) ); - + nodeActions.add(new ParseColAction(Bundle.SQLiteTableView_DisplayAs_text(), outline) ); + explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows, nodeActions), true))); return false; } @@ -173,26 +183,77 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { // End of variables declaration//GEN-END:variables -private class ParseColAction extends AbstractAction { + /** + * Action to handle "Display as" menu item. + * + */ + private class ParseColAction extends AbstractAction implements Presenter.Popup { private final Outline outline; - private TableCellRenderer colCellRenderer; + private final String displayName; - - private ParseColAction(String displayName, Outline outline, TableCellRenderer colCellRenderer ) { + ParseColAction(String displayName, Outline outline ) { super(displayName); this.outline = outline; - this.colCellRenderer = colCellRenderer; + this.displayName = displayName; } @Override public void actionPerformed(ActionEvent e) { + + } - int selCol = outline.getSelectedColumn(); - - TableColumnModel columnModel = outline.getColumnModel(); - - TableColumn column = columnModel.getColumn(selCol); - column.setCellRenderer(colCellRenderer); + @Override + public JMenuItem getPopupPresenter() { + return new DisplayColAsMenu(); + } + + /** + * Class to SubMenu for "Display As" menu + */ + private class DisplayColAsMenu extends JMenu { + + DisplayColAsMenu() { + super(displayName); + initMenu(); + } + + final void initMenu() { + + int selCol = outline.getSelectedColumn(); + if (selCol < 0 ) { + selCol = 1; + } + + TableColumnModel columnModel = outline.getColumnModel(); + TableColumn column = columnModel.getColumn(selCol); + + JMenuItem parseAsEpochItem = new JMenuItem(Bundle.SQLiteTableView_DisplayAsMenuItem_Date()); + parseAsEpochItem.addActionListener((ActionEvent evt) -> { + column.setCellRenderer(new EpochTimeCellRenderer(true)); + }); + parseAsEpochItem.setEnabled(false); + add(parseAsEpochItem); + + JMenuItem parseAsOriginalItem = new JMenuItem(Bundle.SQLiteTableView_DisplayAsMenuItem_RawData()); + parseAsOriginalItem.addActionListener((ActionEvent evt) -> { + column.setCellRenderer(new EpochTimeCellRenderer(false)); + }); + parseAsOriginalItem.setEnabled(false); + add(parseAsOriginalItem); + + // Enable the relevant menuitem based on the current display state of the column + TableCellRenderer currRenderer = column.getCellRenderer(); + if (currRenderer instanceof EpochTimeCellRenderer) { + if (((EpochTimeCellRenderer) currRenderer).isRenderingAsEpoch()) { + parseAsOriginalItem.setEnabled(true); + } else { + parseAsEpochItem.setEnabled(true); + } + } + else { + parseAsEpochItem.setEnabled(true); + } + } } } }