19 package org.sleuthkit.autopsy.corecomponents;
21 import java.awt.Color;
22 import java.awt.Cursor;
23 import java.awt.Dialog;
24 import java.awt.EventQueue;
25 import java.beans.PropertyChangeEvent;
26 import java.beans.PropertyChangeListener;
27 import java.util.Arrays;
28 import java.util.List;
30 import java.util.concurrent.ExecutionException;
31 import java.util.function.Function;
32 import java.util.logging.Level;
33 import java.util.prefs.Preferences;
34 import java.util.stream.Collectors;
35 import javax.swing.JOptionPane;
36 import javax.swing.ListSelectionModel;
37 import javax.swing.SortOrder;
38 import javax.swing.SwingWorker;
39 import org.apache.commons.lang3.StringUtils;
40 import org.netbeans.api.progress.ProgressHandle;
41 import org.openide.DialogDescriptor;
42 import org.openide.DialogDisplayer;
43 import org.openide.NotifyDescriptor;
44 import org.openide.explorer.ExplorerManager;
45 import org.openide.nodes.AbstractNode;
46 import org.openide.nodes.Children;
47 import org.openide.nodes.Node;
48 import org.openide.nodes.NodeEvent;
49 import org.openide.nodes.NodeListener;
50 import org.openide.nodes.NodeMemberEvent;
51 import org.openide.nodes.NodeReorderEvent;
52 import org.openide.util.NbBundle;
53 import org.openide.util.NbPreferences;
74 final class DataResultViewerThumbnail
extends AbstractDataResultViewer {
76 private static final long serialVersionUID = 1L;
77 private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
79 private int totalPages;
80 private int curPageImages;
81 private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
82 private final PageUpdater pageUpdater =
new PageUpdater();
83 private TableFilterNode tfn;
91 DataResultViewerThumbnail(ExplorerManager explorerManager) {
92 super(explorerManager);
100 DataResultViewerThumbnail() {
104 @NbBundle.Messages({
"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
105 "DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails",
106 "DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails"
108 private void initialize() {
110 iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
111 em.addPropertyChangeListener(
new ExplorerManagerNodeSelectionListener());
112 thumbnailSizeComboBox.setModel(
new javax.swing.DefaultComboBoxModel<>(
113 new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
114 Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(),
115 Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()}));
116 thumbnailSizeComboBox.setSelectedIndex(1);
127 @SuppressWarnings(
"unchecked")
129 private
void initComponents() {
131 pageLabel =
new javax.swing.JLabel();
132 pagesLabel =
new javax.swing.JLabel();
133 pagePrevButton =
new javax.swing.JButton();
134 pageNextButton =
new javax.swing.JButton();
135 imagesLabel =
new javax.swing.JLabel();
136 imagesRangeLabel =
new javax.swing.JLabel();
137 pageNumLabel =
new javax.swing.JLabel();
138 filePathLabel =
new javax.swing.JLabel();
139 goToPageLabel =
new javax.swing.JLabel();
140 goToPageField =
new javax.swing.JTextField();
141 thumbnailSizeComboBox =
new javax.swing.JComboBox<>();
142 iconView =
new org.openide.explorer.view.IconView();
143 sortButton =
new javax.swing.JButton();
144 sortLabel =
new javax.swing.JLabel();
146 pageLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.pageLabel.text"));
148 pagesLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.pagesLabel.text"));
150 pagePrevButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_back.png")));
151 pagePrevButton.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.pagePrevButton.text"));
152 pagePrevButton.setDisabledIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png")));
153 pagePrevButton.setFocusable(
false);
154 pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
155 pagePrevButton.setMargin(
new java.awt.Insets(2, 0, 2, 0));
156 pagePrevButton.setPreferredSize(
new java.awt.Dimension(55, 23));
157 pagePrevButton.setRolloverIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png")));
158 pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
159 pagePrevButton.addActionListener(
new java.awt.event.ActionListener() {
160 public void actionPerformed(java.awt.event.ActionEvent evt) {
161 pagePrevButtonActionPerformed(evt);
165 pageNextButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png")));
166 pageNextButton.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.pageNextButton.text"));
167 pageNextButton.setDisabledIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png")));
168 pageNextButton.setFocusable(
false);
169 pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
170 pageNextButton.setMargin(
new java.awt.Insets(2, 0, 2, 0));
171 pageNextButton.setMaximumSize(
new java.awt.Dimension(27, 23));
172 pageNextButton.setMinimumSize(
new java.awt.Dimension(27, 23));
173 pageNextButton.setRolloverIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png")));
174 pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
175 pageNextButton.addActionListener(
new java.awt.event.ActionListener() {
176 public void actionPerformed(java.awt.event.ActionEvent evt) {
177 pageNextButtonActionPerformed(evt);
181 imagesLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.imagesLabel.text"));
183 imagesRangeLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.imagesRangeLabel.text"));
185 pageNumLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.pageNumLabel.text"));
187 filePathLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.filePathLabel.text"));
189 goToPageLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.goToPageLabel.text"));
191 goToPageField.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.goToPageField.text"));
192 goToPageField.addActionListener(
new java.awt.event.ActionListener() {
193 public void actionPerformed(java.awt.event.ActionEvent evt) {
194 goToPageFieldActionPerformed(evt);
198 thumbnailSizeComboBox.addActionListener(
new java.awt.event.ActionListener() {
199 public void actionPerformed(java.awt.event.ActionEvent evt) {
200 thumbnailSizeComboBoxActionPerformed(evt);
204 sortButton.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.sortButton.text"));
205 sortButton.addActionListener(
new java.awt.event.ActionListener() {
206 public void actionPerformed(java.awt.event.ActionEvent evt) {
207 sortButtonActionPerformed(evt);
211 sortLabel.setText(
org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class,
"DataResultViewerThumbnail.sortLabel.text"));
213 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
214 this.setLayout(layout);
215 layout.setHorizontalGroup(
216 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
217 .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
219 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
220 .addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
221 .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
222 .addComponent(pageLabel)
223 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
224 .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE)
225 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
226 .addComponent(pagesLabel)
227 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
228 .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
230 .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
231 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
232 .addComponent(goToPageLabel)
233 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
234 .addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE)
236 .addComponent(imagesLabel)
237 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
238 .addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)
239 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
240 .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
242 .addComponent(sortButton)
243 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
244 .addComponent(sortLabel))
245 .addComponent(filePathLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
248 layout.setVerticalGroup(
249 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
250 .addGroup(layout.createSequentialGroup()
251 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
252 .addComponent(pageLabel)
253 .addComponent(pageNumLabel)
254 .addComponent(pagesLabel)
255 .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
256 .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
257 .addComponent(goToPageLabel)
258 .addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
259 .addComponent(imagesLabel)
260 .addComponent(imagesRangeLabel)
261 .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
262 .addComponent(sortButton)
263 .addComponent(sortLabel))
265 .addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 322, Short.MAX_VALUE)
266 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
267 .addComponent(filePathLabel))
271 private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {
275 private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {
279 private void goToPageFieldActionPerformed(java.awt.event.ActionEvent evt) {
280 goToPage(goToPageField.getText());
283 private void thumbnailSizeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {
285 switch (thumbnailSizeComboBox.getSelectedIndex()) {
287 newIconSize = ImageUtils.ICON_SIZE_SMALL;
290 newIconSize = ImageUtils.ICON_SIZE_LARGE;
294 newIconSize = ImageUtils.ICON_SIZE_MEDIUM;
298 if (iconSize != newIconSize) {
299 iconSize = newIconSize;
300 Node root = em.getRootContext();
301 for (Children c : Arrays.asList(root.getChildren())) {
302 ((ThumbnailViewChildren) c).setIconSize(iconSize);
305 for (Node page : root.getChildren().getNodes()) {
306 for (Node node : page.getChildren().getNodes()) {
307 ((ThumbnailViewChildren.ThumbnailViewNode) node).setIconSize(iconSize);
316 em.setExploredContext(root);
321 private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {
322 List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100);
323 SortChooser sortChooser =
new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn));
324 DialogDescriptor dialogDescriptor =
new DialogDescriptor(sortChooser, sortChooser.getDialogTitle());
325 Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
326 createDialog.setVisible(
true);
327 final Object dialogReturnValue = dialogDescriptor.getValue();
328 if (DialogDescriptor.OK_OPTION == dialogReturnValue) {
330 List<SortCriterion> criteria = sortChooser.getCriteria();
331 final Preferences preferences = NbPreferences.forModule(DataResultViewerThumbnail.class);
333 Map<Node.Property<?>, SortCriterion> criteriaMap = criteria.stream()
334 .collect(Collectors.toMap(SortCriterion::getProperty,
339 int numProperties = childProperties.size();
340 for (
int i = 0; i < numProperties; i++) {
341 Node.Property<?> prop = childProperties.get(i);
342 String propName = prop.getName();
343 SortCriterion criterion = criteriaMap.get(prop);
344 final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName);
345 final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName);
347 if (criterion != null) {
348 preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING);
349 preferences.putInt(columnSortRankKey, criterion.getSortRank() + 1);
351 preferences.remove(columnSortOrderKey);
352 preferences.remove(columnSortRankKey);
361 private javax.swing.JLabel filePathLabel;
362 private javax.swing.JTextField goToPageField;
363 private javax.swing.JLabel goToPageLabel;
364 private org.openide.explorer.view.IconView iconView;
365 private javax.swing.JLabel imagesLabel;
366 private javax.swing.JLabel imagesRangeLabel;
367 private javax.swing.JLabel pageLabel;
368 private javax.swing.JButton pageNextButton;
369 private javax.swing.JLabel pageNumLabel;
370 private javax.swing.JButton pagePrevButton;
371 private javax.swing.JLabel pagesLabel;
372 private javax.swing.JButton sortButton;
373 private javax.swing.JLabel sortLabel;
374 private javax.swing.JComboBox<String> thumbnailSizeComboBox;
378 public boolean isSupported(Node selectedNode) {
379 if (selectedNode == null) {
386 public void setNode(Node givenNode) {
387 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
389 if (givenNode != null) {
390 tfn = (TableFilterNode) givenNode;
396 ThumbnailViewChildren childNode =
new ThumbnailViewChildren(givenNode, iconSize);
397 final Node root =
new AbstractNode(childNode);
399 pageUpdater.setRoot(root);
400 root.addNodeListener(pageUpdater);
401 em.setRootContext(root);
404 Node emptyNode =
new AbstractNode(Children.LEAF);
405 em.setRootContext(emptyNode);
406 iconView.setBackground(Color.BLACK);
409 this.setCursor(null);
414 public String getTitle() {
415 return NbBundle.getMessage(this.getClass(),
"DataResultViewerThumbnail.title");
419 public DataResultViewer createInstance() {
420 return new DataResultViewerThumbnail();
424 public void resetComponent() {
425 super.resetComponent();
433 public void clearComponent() {
434 this.iconView.removeAll();
435 this.iconView = null;
436 super.clearComponent();
439 private void nextPage() {
440 if (curPage < totalPages) {
446 private void previousPage() {
453 private void goToPage(String pageNumText) {
456 newPage = Integer.parseInt(pageNumText);
457 }
catch (NumberFormatException e) {
462 if (newPage > totalPages || newPage < 1) {
463 JOptionPane.showMessageDialog(
this,
464 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.msgDlg", totalPages),
465 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.err"),
466 JOptionPane.WARNING_MESSAGE);
474 private void switchPage() {
476 EventQueue.invokeLater(() -> {
477 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
481 new SwingWorker<Object, Void>() {
482 private ProgressHandle progress;
485 protected Object doInBackground() throws Exception {
486 pagePrevButton.setEnabled(
false);
487 pageNextButton.setEnabled(
false);
488 goToPageField.setEnabled(
false);
489 progress = ProgressHandle.createHandle(
490 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.genThumbs"));
492 progress.switchToIndeterminate();
493 Node root = em.getRootContext();
494 Node pageNode = root.getChildren().getNodeAt(curPage - 1);
495 em.setExploredContext(pageNode);
496 curPageImages = pageNode.getChildren().getNodesCount();
501 protected void done() {
508 }
catch (InterruptedException | ExecutionException ex) {
510 new NotifyDescriptor.Message(
511 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.switchPage.done.errMsg",
513 NotifyDescriptor.ERROR_MESSAGE);
514 DialogDisplayer.getDefault().notify(d);
515 logger.log(Level.SEVERE,
"Error making thumbnails: {0}", ex.getMessage());
517 catch (java.util.concurrent.CancellationException ex) {
525 "# {0} - sort criteria",
"DataResultViewerThumbnail.sortLabel.textTemplate=Sorted by: {0}",
526 "DataResultViewerThumbnail.sortLabel.text=Sorted by: ---"})
527 private void updateControls() {
528 if (totalPages == 0) {
529 pagePrevButton.setEnabled(
false);
530 pageNextButton.setEnabled(
false);
531 goToPageField.setEnabled(
false);
532 pageNumLabel.setText(
"");
533 imagesRangeLabel.setText(
"");
534 thumbnailSizeComboBox.setEnabled(
false);
535 sortButton.setEnabled(
false);
536 sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
539 pageNumLabel.setText(
540 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.pageNumbers.curOfTotal",
541 Integer.toString(curPage), Integer.toString(totalPages)));
542 final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
543 final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
544 imagesRangeLabel.setText(imagesFrom +
"-" + imagesTo);
546 pageNextButton.setEnabled(!(curPage == totalPages));
547 pagePrevButton.setEnabled(!(curPage == 1));
548 goToPageField.setEnabled(totalPages > 1);
549 sortButton.setEnabled(
true);
550 thumbnailSizeComboBox.setEnabled(
true);
552 String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream()
553 .map(SortCriterion::toString)
554 .collect(Collectors.joining(
" "));
555 sortString = StringUtils.defaultIfBlank(sortString,
"---");
556 sortLabel.setText(Bundle.DataResultViewerThumbnail_sortLabel_textTemplate(sortString));
558 sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
570 void setRoot(Node root) {
580 totalPages = root.getChildren().getNodesCount();
582 if (totalPages == 0) {
588 if (curPage == -1 || curPage > totalPages) {
593 final Node pageNode = root.getChildren().getNodeAt(curPage - 1);
596 if (pageNode != null) {
597 pageNode.addNodeListener(
new NodeListener() {
600 curPageImages = pageNode.getChildren().getNodesCount();
623 em.setExploredContext(pageNode);
649 if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
650 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
652 Node[] selectedNodes = em.getSelectedNodes();
653 if (selectedNodes.length == 1) {
654 AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
656 filePathLabel.setText(
"");
659 String uPath = af.getUniquePath();
660 filePathLabel.setText(uPath);
661 filePathLabel.setToolTipText(uPath);
662 }
catch (TskCoreException e) {
663 logger.log(Level.WARNING,
"Could not get unique path for content: {0}", af.getName());
667 filePathLabel.setText(
"");
void propertyChange(PropertyChangeEvent evt)
void nodeDestroyed(NodeEvent ne)
void childrenAdded(NodeMemberEvent nme)
void childrenRemoved(NodeMemberEvent nme)
void propertyChange(PropertyChangeEvent evt)
void childrenReordered(NodeReorderEvent nre)