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.List;
29 import java.util.concurrent.ExecutionException;
30 import java.util.function.Function;
31 import java.util.logging.Level;
32 import java.util.prefs.Preferences;
33 import java.util.stream.Collectors;
34 import javax.swing.JOptionPane;
35 import javax.swing.ListSelectionModel;
36 import javax.swing.SortOrder;
37 import javax.swing.SwingWorker;
38 import org.apache.commons.lang3.StringUtils;
39 import org.netbeans.api.progress.ProgressHandle;
40 import org.openide.DialogDescriptor;
41 import org.openide.DialogDisplayer;
42 import org.openide.NotifyDescriptor;
43 import org.openide.explorer.ExplorerManager;
44 import org.openide.nodes.AbstractNode;
45 import org.openide.nodes.Children;
46 import org.openide.nodes.Node;
47 import org.openide.nodes.NodeEvent;
48 import org.openide.nodes.NodeListener;
49 import org.openide.nodes.NodeMemberEvent;
50 import org.openide.nodes.NodeReorderEvent;
51 import org.openide.util.NbBundle;
52 import org.openide.util.NbPreferences;
73 final class DataResultViewerThumbnail
extends AbstractDataResultViewer {
75 private static final long serialVersionUID = 1L;
76 private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
78 private int totalPages;
79 private int curPageImages;
80 private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
81 private final PageUpdater pageUpdater =
new PageUpdater();
82 private TableFilterNode tfn;
83 private ThumbnailViewChildren tvc;
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 (thumbSize != newIconSize) {
299 thumbSize = newIconSize;
300 Node root = em.getRootContext();
301 ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize);
310 em.setExploredContext(root);
315 private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {
316 List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100);
317 SortChooser sortChooser =
new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn));
318 DialogDescriptor dialogDescriptor =
new DialogDescriptor(sortChooser, sortChooser.getDialogTitle());
319 Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
320 createDialog.setVisible(
true);
321 final Object dialogReturnValue = dialogDescriptor.getValue();
322 if (DialogDescriptor.OK_OPTION == dialogReturnValue) {
324 List<SortCriterion> criteria = sortChooser.getCriteria();
325 final Preferences preferences = NbPreferences.forModule(DataResultViewerThumbnail.class);
327 Map<Node.Property<?>, SortCriterion> criteriaMap = criteria.stream()
328 .collect(Collectors.toMap(SortCriterion::getProperty,
333 int numProperties = childProperties.size();
334 for (
int i = 0; i < numProperties; i++) {
335 Node.Property<?> prop = childProperties.get(i);
336 String propName = prop.getName();
337 SortCriterion criterion = criteriaMap.get(prop);
338 final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName);
339 final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName);
341 if (criterion != null) {
342 preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING);
343 preferences.putInt(columnSortRankKey, criterion.getSortRank() + 1);
345 preferences.remove(columnSortOrderKey);
346 preferences.remove(columnSortRankKey);
355 private javax.swing.JLabel filePathLabel;
356 private javax.swing.JTextField goToPageField;
357 private javax.swing.JLabel goToPageLabel;
358 private org.openide.explorer.view.IconView iconView;
359 private javax.swing.JLabel imagesLabel;
360 private javax.swing.JLabel imagesRangeLabel;
361 private javax.swing.JLabel pageLabel;
362 private javax.swing.JButton pageNextButton;
363 private javax.swing.JLabel pageNumLabel;
364 private javax.swing.JButton pagePrevButton;
365 private javax.swing.JLabel pagesLabel;
366 private javax.swing.JButton sortButton;
367 private javax.swing.JLabel sortLabel;
368 private javax.swing.JComboBox<String> thumbnailSizeComboBox;
372 public boolean isSupported(Node selectedNode) {
373 if (selectedNode == null) {
380 public void setNode(Node givenNode) {
381 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
383 tvc.cancelLoadingThumbnails();
386 if (givenNode != null) {
387 tfn = (TableFilterNode) givenNode;
393 tvc =
new ThumbnailViewChildren(givenNode,thumbSize);
394 final Node root =
new AbstractNode(tvc);
396 pageUpdater.setRoot(root);
397 root.addNodeListener(pageUpdater);
398 em.setRootContext(root);
402 Node emptyNode =
new AbstractNode(Children.LEAF);
403 em.setRootContext(emptyNode);
404 iconView.setBackground(Color.BLACK);
407 this.setCursor(null);
412 public String getTitle() {
413 return NbBundle.getMessage(this.getClass(),
"DataResultViewerThumbnail.title");
417 public DataResultViewer createInstance() {
418 return new DataResultViewerThumbnail();
422 public void resetComponent() {
423 super.resetComponent();
431 public void clearComponent() {
432 this.iconView.removeAll();
433 this.iconView = null;
434 super.clearComponent();
437 private void nextPage() {
438 if (curPage < totalPages) {
444 private void previousPage() {
451 private void goToPage(String pageNumText) {
454 newPage = Integer.parseInt(pageNumText);
455 }
catch (NumberFormatException e) {
460 if (newPage > totalPages || newPage < 1) {
461 JOptionPane.showMessageDialog(
this,
462 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.msgDlg", totalPages),
463 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.err"),
464 JOptionPane.WARNING_MESSAGE);
472 private void switchPage() {
474 EventQueue.invokeLater(() -> {
475 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
479 new SwingWorker<Object, Void>() {
480 private ProgressHandle progress;
483 protected Object doInBackground() throws Exception {
484 pagePrevButton.setEnabled(
false);
485 pageNextButton.setEnabled(
false);
486 goToPageField.setEnabled(
false);
487 progress = ProgressHandle.createHandle(
488 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.genThumbs"));
490 progress.switchToIndeterminate();
491 Node root = em.getRootContext();
492 Node pageNode = root.getChildren().getNodeAt(curPage - 1);
493 em.setExploredContext(pageNode);
494 curPageImages = pageNode.getChildren().getNodesCount();
499 protected void done() {
506 }
catch (InterruptedException | ExecutionException ex) {
508 new NotifyDescriptor.Message(
509 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.switchPage.done.errMsg",
511 NotifyDescriptor.ERROR_MESSAGE);
512 DialogDisplayer.getDefault().notify(d);
513 logger.log(Level.SEVERE,
"Error making thumbnails: {0}", ex.getMessage());
515 catch (java.util.concurrent.CancellationException ex) {
523 "# {0} - sort criteria",
"DataResultViewerThumbnail.sortLabel.textTemplate=Sorted by: {0}",
524 "DataResultViewerThumbnail.sortLabel.text=Sorted by: ---"})
525 private void updateControls() {
526 if (totalPages == 0) {
527 pagePrevButton.setEnabled(
false);
528 pageNextButton.setEnabled(
false);
529 goToPageField.setEnabled(
false);
530 pageNumLabel.setText(
"");
531 imagesRangeLabel.setText(
"");
532 thumbnailSizeComboBox.setEnabled(
false);
533 sortButton.setEnabled(
false);
534 sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
537 pageNumLabel.setText(
538 NbBundle.getMessage(
this.getClass(),
"DataResultViewerThumbnail.pageNumbers.curOfTotal",
539 Integer.toString(curPage), Integer.toString(totalPages)));
540 final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
541 final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
542 imagesRangeLabel.setText(imagesFrom +
"-" + imagesTo);
544 pageNextButton.setEnabled(!(curPage == totalPages));
545 pagePrevButton.setEnabled(!(curPage == 1));
546 goToPageField.setEnabled(totalPages > 1);
547 sortButton.setEnabled(
true);
548 thumbnailSizeComboBox.setEnabled(
true);
550 String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream()
551 .map(SortCriterion::toString)
552 .collect(Collectors.joining(
" "));
553 sortString = StringUtils.defaultIfBlank(sortString,
"---");
554 sortLabel.setText(Bundle.DataResultViewerThumbnail_sortLabel_textTemplate(sortString));
556 sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
568 void setRoot(Node root) {
578 totalPages = root.getChildren().getNodesCount();
580 if (totalPages == 0) {
586 if (curPage == -1 || curPage > totalPages) {
591 final Node pageNode = root.getChildren().getNodeAt(curPage - 1);
594 if (pageNode != null) {
595 pageNode.addNodeListener(
new NodeListener() {
598 curPageImages = pageNode.getChildren().getNodesCount();
621 em.setExploredContext(pageNode);
647 if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
648 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
650 Node[] selectedNodes = em.getSelectedNodes();
651 if (selectedNodes.length == 1) {
652 AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
654 filePathLabel.setText(
"");
657 String uPath = af.getUniquePath();
658 filePathLabel.setText(uPath);
659 filePathLabel.setToolTipText(uPath);
660 }
catch (TskCoreException e) {
661 logger.log(Level.WARNING,
"Could not get unique path for content: {0}", af.getName());
665 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)