19 package org.sleuthkit.autopsy.contentviewers;
21 import java.awt.EventQueue;
22 import java.awt.event.ActionEvent;
23 import java.awt.image.BufferedImage;
24 import java.beans.PropertyChangeEvent;
25 import java.beans.PropertyChangeListener;
26 import java.beans.PropertyChangeSupport;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.List;
34 import static java.util.Objects.nonNull;
35 import java.util.SortedSet;
36 import java.util.concurrent.ExecutionException;
37 import java.util.logging.Level;
38 import java.util.stream.Collectors;
39 import javafx.application.Platform;
40 import javafx.collections.ListChangeListener.Change;
41 import javafx.concurrent.Task;
42 import javafx.embed.swing.JFXPanel;
43 import javafx.geometry.Pos;
44 import javafx.geometry.Rectangle2D;
45 import javafx.scene.Cursor;
46 import javafx.scene.Group;
47 import javafx.scene.Scene;
48 import javafx.scene.control.Button;
49 import javafx.scene.control.Label;
50 import javafx.scene.control.ProgressBar;
51 import javafx.scene.control.ScrollPane;
52 import javafx.scene.control.ScrollPane.ScrollBarPolicy;
53 import javafx.scene.image.Image;
54 import javafx.scene.image.ImageView;
55 import javafx.scene.layout.VBox;
56 import javafx.scene.transform.Rotate;
57 import javafx.scene.transform.Scale;
58 import javafx.scene.transform.Translate;
59 import javax.imageio.ImageIO;
60 import javax.swing.JFileChooser;
61 import javafx.scene.Node;
62 import javax.swing.JMenuItem;
63 import javax.swing.JOptionPane;
64 import javax.swing.JPanel;
65 import javax.swing.JPopupMenu;
66 import javax.swing.JSeparator;
67 import javax.swing.SwingUtilities;
68 import javax.swing.SwingWorker;
69 import org.apache.commons.io.FilenameUtils;
70 import org.controlsfx.control.MaskerPane;
71 import org.openide.util.NbBundle;
72 import org.python.google.common.collect.Lists;
98 @NbBundle.Messages({
"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E",
99 "MediaViewImagePanel.errorLabel.text=Could not load file into Media View.",
100 "MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory."})
101 @SuppressWarnings(
"PMD.SingularField")
102 class MediaViewImagePanel
extends JPanel implements MediaFileViewer.MediaViewPanel {
104 private static final Image EXTERNAL =
new Image(MediaViewImagePanel.class.getResource(
"/org/sleuthkit/autopsy/images/external.png").toExternalForm());
105 private final static Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName());
107 private final boolean fxInited;
109 private JFXPanel fxPanel;
110 private AbstractFile file;
111 private Group masterGroup;
112 private ImageTagsGroup tagsGroup;
113 private ImageTagCreator imageTagCreator;
114 private ImageView fxImageView;
115 private ScrollPane scrollPane;
116 private final ProgressBar progressBar =
new ProgressBar();
117 private final MaskerPane maskerPane =
new MaskerPane();
119 private final JPopupMenu popupMenu =
new JPopupMenu();
120 private final JMenuItem createTagMenuItem;
121 private final JMenuItem deleteTagMenuItem;
122 private final JMenuItem hideTagsMenuItem;
123 private final JMenuItem exportTagsMenuItem;
125 private final JFileChooser exportChooser;
127 private final PropertyChangeSupport pcs =
new PropertyChangeSupport(
this);
129 private double zoomRatio;
130 private double rotation;
132 private static final double[] ZOOM_STEPS = {
133 0.0625, 0.125, 0.25, 0.375, 0.5, 0.75,
134 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10};
136 private static final double MIN_ZOOM_RATIO = 0.0625;
137 private static final double MAX_ZOOM_RATIO = 10.0;
140 ImageIO.scanForPlugins();
147 static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedImageMimeTypes();
152 static private final List<String> supportedExtensions = ImageUtils.getSupportedImageExtensions().stream()
154 .collect(Collectors.toList());
156 private Task<Image> readImageTask;
161 public MediaViewImagePanel() {
165 exportChooser =
new JFileChooser();
166 exportChooser.setDialogTitle(Bundle.MediaViewImagePanel_fileChooserTitle());
169 createTagMenuItem =
new JMenuItem(
"Create");
170 createTagMenuItem.addActionListener((event) -> createTag());
171 popupMenu.add(createTagMenuItem);
173 popupMenu.add(
new JSeparator());
175 deleteTagMenuItem =
new JMenuItem(
"Delete");
176 deleteTagMenuItem.addActionListener((event) -> deleteTag());
177 popupMenu.add(deleteTagMenuItem);
179 popupMenu.add(
new JSeparator());
181 hideTagsMenuItem =
new JMenuItem(
"Hide");
182 hideTagsMenuItem.addActionListener((event) -> showOrHideTags());
183 popupMenu.add(hideTagsMenuItem);
185 popupMenu.add(
new JSeparator());
187 exportTagsMenuItem =
new JMenuItem(
"Export");
188 exportTagsMenuItem.addActionListener((event) -> exportTags());
189 popupMenu.add(exportTagsMenuItem);
191 popupMenu.setPopupSize(300, 150);
194 Platform.runLater(
new Runnable() {
198 fxImageView =
new ImageView();
199 masterGroup =
new Group(fxImageView);
200 tagsGroup =
new ImageTagsGroup(fxImageView);
201 tagsGroup.getChildren().addListener((Change<? extends Node> c) -> {
202 if (c.getList().isEmpty()) {
203 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
204 "state", null, State.EMPTY));
208 subscribeTagMenuItemsToStateChanges();
210 masterGroup.getChildren().add(tagsGroup);
213 tagsGroup.addFocusChangeListener((event) -> {
214 if (event.getPropertyName().equals(ImageTagControls.NOT_FOCUSED.getName())) {
215 if (masterGroup.getChildren().contains(imageTagCreator)) {
219 if (tagsGroup.getChildren().isEmpty()) {
220 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
221 "state", null, State.EMPTY));
223 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
224 "state", null, State.CREATE));
226 }
else if (event.getPropertyName().equals(ImageTagControls.FOCUSED.getName())) {
227 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
228 "state", null, State.SELECTED));
232 scrollPane =
new ScrollPane(masterGroup);
233 scrollPane.getStyleClass().add(
"bg");
234 scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
235 scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
237 fxPanel =
new JFXPanel();
238 Scene scene =
new Scene(scrollPane);
239 scene.getStylesheets().add(MediaViewImagePanel.class.getResource(
"MediaViewImagePanel.css").toExternalForm());
240 fxPanel.setScene(scene);
242 fxImageView.setSmooth(
true);
243 fxImageView.setCache(
true);
245 EventQueue.invokeLater(() -> {
258 private void subscribeTagMenuItemsToStateChanges() {
259 pcs.addPropertyChangeListener((event) -> {
260 State currentState = (State) event.getNewValue();
261 switch (currentState) {
263 createTagMenuItem.setEnabled(
true);
264 deleteTagMenuItem.setEnabled(
false);
265 hideTagsMenuItem.setEnabled(
true);
266 exportTagsMenuItem.setEnabled(
true);
269 if (masterGroup.getChildren().contains(imageTagCreator)) {
270 imageTagCreator.disconnect();
271 masterGroup.getChildren().remove(imageTagCreator);
273 createTagMenuItem.setEnabled(
false);
274 deleteTagMenuItem.setEnabled(
true);
275 hideTagsMenuItem.setEnabled(
true);
276 exportTagsMenuItem.setEnabled(
true);
279 createTagMenuItem.setEnabled(
false);
280 deleteTagMenuItem.setEnabled(
false);
281 hideTagsMenuItem.setEnabled(
true);
282 hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
283 exportTagsMenuItem.setEnabled(
false);
286 createTagMenuItem.setEnabled(
true);
287 deleteTagMenuItem.setEnabled(
false);
288 hideTagsMenuItem.setEnabled(
true);
289 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
290 exportTagsMenuItem.setEnabled(
true);
294 if (masterGroup.getChildren().contains(imageTagCreator)) {
295 imageTagCreator.disconnect();
297 createTagMenuItem.setEnabled(
true);
298 deleteTagMenuItem.setEnabled(
false);
299 hideTagsMenuItem.setEnabled(
false);
300 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
301 exportTagsMenuItem.setEnabled(
false);
304 createTagMenuItem.setEnabled(
true);
305 deleteTagMenuItem.setEnabled(
false);
306 hideTagsMenuItem.setEnabled(
true);
307 exportTagsMenuItem.setEnabled(
true);
310 createTagMenuItem.setEnabled(
false);
311 deleteTagMenuItem.setEnabled(
false);
312 hideTagsMenuItem.setEnabled(
false);
313 exportTagsMenuItem.setEnabled(
false);
321 public boolean isInited() {
328 public void reset() {
329 Platform.runLater(() -> {
330 fxImageView.setViewport(
new Rectangle2D(0, 0, 0, 0));
331 fxImageView.setImage(null);
332 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
333 "state", null, State.DEFAULT));
334 masterGroup.getChildren().clear();
335 scrollPane.setContent(null);
336 scrollPane.setContent(masterGroup);
340 private void showErrorNode(String errorMessage, AbstractFile file) {
341 final Button externalViewerButton =
new Button(Bundle.MediaViewImagePanel_externalViewerButton_text(),
new ImageView(EXTERNAL));
342 externalViewerButton.setOnAction(actionEvent
347 new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(),
new FileNode(file))
348 .actionPerformed(
new ActionEvent(
this, ActionEvent.ACTION_PERFORMED,
""))
351 final VBox errorNode =
new VBox(10,
new Label(errorMessage), externalViewerButton);
352 errorNode.setAlignment(Pos.CENTER);
360 void showImageFx(
final AbstractFile file) {
365 Platform.runLater(() -> {
366 if (readImageTask != null) {
367 readImageTask.cancel();
369 readImageTask = ImageUtils.newReadImageTask(file);
370 readImageTask.setOnSucceeded(succeeded -> {
371 if (!Case.isCaseOpen()) {
383 Image fxImage = readImageTask.get();
384 masterGroup.getChildren().clear();
385 tagsGroup.getChildren().clear();
387 if (nonNull(fxImage)) {
389 fxImageView.setImage(fxImage);
391 masterGroup.getChildren().add(fxImageView);
392 masterGroup.getChildren().add(tagsGroup);
395 List<ContentTag> tags = Case.getCurrentCase().getServices()
396 .getTagsManager().getContentTagsByContent(file);
398 List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
400 tagsGroup = buildImageTagsGroup(contentViewerTags);
401 if (!tagsGroup.getChildren().isEmpty()) {
402 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
403 "state", null, State.NONEMPTY));
405 }
catch (TskCoreException | NoCurrentCaseException ex) {
406 LOGGER.log(Level.WARNING,
"Could not retrieve image tags for file in case db", ex);
408 scrollPane.setContent(masterGroup);
410 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
412 }
catch (InterruptedException | ExecutionException ex) {
413 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
415 scrollPane.setCursor(Cursor.DEFAULT);
417 readImageTask.setOnFailed(failed -> {
418 if (!Case.isCaseOpen()) {
428 Throwable exception = readImageTask.getException();
429 if (exception instanceof OutOfMemoryError
430 && exception.getMessage().contains(
"Java heap space")) {
431 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_OOMText(), file);
433 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
436 scrollPane.setCursor(Cursor.DEFAULT);
439 maskerPane.setProgressNode(progressBar);
440 progressBar.progressProperty().bind(readImageTask.progressProperty());
441 maskerPane.textProperty().bind(readImageTask.messageProperty());
442 scrollPane.setContent(null);
443 scrollPane.setCursor(Cursor.WAIT);
444 new Thread(readImageTask).start();
457 private List<ContentViewerTag<ImageTagRegion>> getContentViewerTags(List<ContentTag> contentTags)
458 throws TskCoreException, NoCurrentCaseException {
459 List<ContentViewerTag<ImageTagRegion>> contentViewerTags =
new ArrayList<>();
460 for (ContentTag contentTag : contentTags) {
461 ContentViewerTag<ImageTagRegion> contentViewerTag = ContentViewerTagManager
462 .getTag(contentTag, ImageTagRegion.class);
463 if (contentViewerTag == null) {
467 contentViewerTags.add(contentViewerTag);
469 return contentViewerTags;
481 private ImageTagsGroup buildImageTagsGroup(List<ContentViewerTag<ImageTagRegion>> contentViewerTags) {
483 contentViewerTags.forEach(contentViewerTag -> {
488 tagsGroup.getChildren().add(buildImageTag(contentViewerTag));
498 public List<String> getSupportedMimeTypes() {
499 return Collections.unmodifiableList(Lists.newArrayList(supportedMimes));
508 public List<String> getSupportedExtensions() {
509 return getExtensions();
517 public List<String> getExtensions() {
518 return Collections.unmodifiableList(supportedExtensions);
522 public boolean isSupported(AbstractFile file) {
523 return ImageUtils.isImageThumbnailSupported(file);
531 @SuppressWarnings(
"unchecked")
533 private
void initComponents() {
535 toolbar =
new javax.swing.JToolBar();
536 rotationTextField =
new javax.swing.JTextField();
537 rotateLeftButton =
new javax.swing.JButton();
538 rotateRightButton =
new javax.swing.JButton();
539 jSeparator1 =
new javax.swing.JToolBar.Separator();
540 zoomTextField =
new javax.swing.JTextField();
541 zoomOutButton =
new javax.swing.JButton();
542 zoomInButton =
new javax.swing.JButton();
543 jSeparator2 =
new javax.swing.JToolBar.Separator();
544 zoomResetButton =
new javax.swing.JButton();
545 filler1 =
new javax.swing.Box.Filler(
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0));
546 filler2 =
new javax.swing.Box.Filler(
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0),
new java.awt.Dimension(32767, 0));
547 jPanel1 =
new javax.swing.JPanel();
548 tagsMenu =
new javax.swing.JButton();
550 setBackground(
new java.awt.Color(0, 0, 0));
551 addComponentListener(
new java.awt.event.ComponentAdapter() {
552 public void componentResized(java.awt.event.ComponentEvent evt) {
553 formComponentResized(evt);
556 setLayout(
new javax.swing.BoxLayout(
this, javax.swing.BoxLayout.Y_AXIS));
558 toolbar.setFloatable(
false);
559 toolbar.setRollover(
true);
560 toolbar.setMaximumSize(
new java.awt.Dimension(32767, 23));
563 rotationTextField.setEditable(
false);
564 rotationTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
565 rotationTextField.setText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotationTextField.text"));
566 rotationTextField.setMaximumSize(
new java.awt.Dimension(50, 2147483647));
567 rotationTextField.setMinimumSize(
new java.awt.Dimension(50, 20));
568 rotationTextField.setPreferredSize(
new java.awt.Dimension(50, 20));
569 toolbar.add(rotationTextField);
571 rotateLeftButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/rotate-left.png")));
572 org.openide.awt.Mnemonics.setLocalizedText(rotateLeftButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateLeftButton.text"));
573 rotateLeftButton.setToolTipText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateLeftButton.toolTipText"));
574 rotateLeftButton.setFocusable(
false);
575 rotateLeftButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
576 rotateLeftButton.setMaximumSize(
new java.awt.Dimension(24, 24));
577 rotateLeftButton.setMinimumSize(
new java.awt.Dimension(24, 24));
578 rotateLeftButton.setPreferredSize(
new java.awt.Dimension(24, 24));
579 rotateLeftButton.addActionListener(
new java.awt.event.ActionListener() {
580 public void actionPerformed(java.awt.event.ActionEvent evt) {
581 rotateLeftButtonActionPerformed(evt);
584 toolbar.add(rotateLeftButton);
586 rotateRightButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/rotate-right.png")));
587 org.openide.awt.Mnemonics.setLocalizedText(rotateRightButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateRightButton.text"));
588 rotateRightButton.setFocusable(
false);
589 rotateRightButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
590 rotateRightButton.setMaximumSize(
new java.awt.Dimension(24, 24));
591 rotateRightButton.setMinimumSize(
new java.awt.Dimension(24, 24));
592 rotateRightButton.setPreferredSize(
new java.awt.Dimension(24, 24));
593 rotateRightButton.addActionListener(
new java.awt.event.ActionListener() {
594 public void actionPerformed(java.awt.event.ActionEvent evt) {
595 rotateRightButtonActionPerformed(evt);
598 toolbar.add(rotateRightButton);
600 jSeparator1.setMaximumSize(
new java.awt.Dimension(6, 20));
601 toolbar.add(jSeparator1);
603 zoomTextField.setEditable(
false);
604 zoomTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
605 zoomTextField.setText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomTextField.text"));
606 zoomTextField.setMaximumSize(
new java.awt.Dimension(50, 2147483647));
607 zoomTextField.setMinimumSize(
new java.awt.Dimension(50, 20));
608 zoomTextField.setPreferredSize(
new java.awt.Dimension(50, 20));
609 toolbar.add(zoomTextField);
611 zoomOutButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/zoom-out.png")));
612 org.openide.awt.Mnemonics.setLocalizedText(zoomOutButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomOutButton.text"));
613 zoomOutButton.setFocusable(
false);
614 zoomOutButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
615 zoomOutButton.setMaximumSize(
new java.awt.Dimension(24, 24));
616 zoomOutButton.setMinimumSize(
new java.awt.Dimension(24, 24));
617 zoomOutButton.setPreferredSize(
new java.awt.Dimension(24, 24));
618 zoomOutButton.addActionListener(
new java.awt.event.ActionListener() {
619 public void actionPerformed(java.awt.event.ActionEvent evt) {
620 zoomOutButtonActionPerformed(evt);
623 toolbar.add(zoomOutButton);
625 zoomInButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/zoom-in.png")));
626 org.openide.awt.Mnemonics.setLocalizedText(zoomInButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomInButton.text"));
627 zoomInButton.setFocusable(
false);
628 zoomInButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
629 zoomInButton.setMaximumSize(
new java.awt.Dimension(24, 24));
630 zoomInButton.setMinimumSize(
new java.awt.Dimension(24, 24));
631 zoomInButton.setPreferredSize(
new java.awt.Dimension(24, 24));
632 zoomInButton.addActionListener(
new java.awt.event.ActionListener() {
633 public void actionPerformed(java.awt.event.ActionEvent evt) {
634 zoomInButtonActionPerformed(evt);
637 toolbar.add(zoomInButton);
639 jSeparator2.setMaximumSize(
new java.awt.Dimension(6, 20));
640 toolbar.add(jSeparator2);
642 org.openide.awt.Mnemonics.setLocalizedText(zoomResetButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomResetButton.text"));
643 zoomResetButton.setFocusable(
false);
644 zoomResetButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
645 zoomResetButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
646 zoomResetButton.addActionListener(
new java.awt.event.ActionListener() {
647 public void actionPerformed(java.awt.event.ActionEvent evt) {
648 zoomResetButtonActionPerformed(evt);
651 toolbar.add(zoomResetButton);
652 toolbar.add(filler1);
653 toolbar.add(filler2);
654 toolbar.add(jPanel1);
656 org.openide.awt.Mnemonics.setLocalizedText(tagsMenu,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.tagsMenu.text_1"));
657 tagsMenu.setFocusable(
false);
658 tagsMenu.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
659 tagsMenu.setMaximumSize(
new java.awt.Dimension(75, 21));
660 tagsMenu.setMinimumSize(
new java.awt.Dimension(75, 21));
661 tagsMenu.setPreferredSize(
new java.awt.Dimension(75, 21));
662 tagsMenu.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
663 tagsMenu.addMouseListener(
new java.awt.event.MouseAdapter() {
664 public void mousePressed(java.awt.event.MouseEvent evt) {
665 tagsMenuMousePressed(evt);
668 toolbar.add(tagsMenu);
673 private void rotateLeftButtonActionPerformed(java.awt.event.ActionEvent evt) {
674 rotation = (rotation + 270) % 360;
678 private void rotateRightButtonActionPerformed(java.awt.event.ActionEvent evt) {
679 rotation = (rotation + 90) % 360;
683 private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {
685 for (
int i = 0; i < ZOOM_STEPS.length; i++) {
686 if (zoomRatio < ZOOM_STEPS[i]) {
687 zoomRatio = ZOOM_STEPS[i];
694 private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {
696 for (
int i = ZOOM_STEPS.length - 1; i >= 0; i--) {
697 if (zoomRatio > ZOOM_STEPS[i]) {
698 zoomRatio = ZOOM_STEPS[i];
705 private void zoomResetButtonActionPerformed(java.awt.event.ActionEvent evt) {
709 private void formComponentResized(java.awt.event.ComponentEvent evt) {
717 private void deleteTag() {
718 Platform.runLater(() -> {
719 ImageTag tagInFocus = tagsGroup.getFocus();
720 if (tagInFocus == null) {
725 ContentViewerTag<ImageTagRegion> contentViewerTag = tagInFocus.getContentViewerTag();
726 scrollPane.setCursor(Cursor.WAIT);
727 ContentViewerTagManager.deleteTag(contentViewerTag);
728 Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(contentViewerTag.getContentTag());
729 tagsGroup.getChildren().remove(tagInFocus);
730 }
catch (TskCoreException | NoCurrentCaseException ex) {
731 LOGGER.log(Level.WARNING,
"Could not delete image tag in case db", ex);
734 scrollPane.setCursor(Cursor.DEFAULT);
737 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
738 "state", null, State.CREATE));
745 private void createTag() {
746 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
747 "state", null, State.DISABLE));
748 imageTagCreator =
new ImageTagCreator(fxImageView);
750 PropertyChangeListener newTagListener = (event) -> {
751 SwingUtilities.invokeLater(() -> {
752 ImageTagRegion tag = (ImageTagRegion) event.getNewValue();
754 TagNameAndComment result = GetTagNameAndCommentDialog.doDialog();
755 if (result != null) {
757 Platform.runLater(() -> {
759 scrollPane.setCursor(Cursor.WAIT);
760 ContentViewerTag<ImageTagRegion> contentViewerTag = storeImageTag(tag, result);
761 ImageTag imageTag = buildImageTag(contentViewerTag);
762 tagsGroup.getChildren().add(imageTag);
763 }
catch (TskCoreException | SerializationException | NoCurrentCaseException ex) {
764 LOGGER.log(Level.WARNING,
"Could not save new image tag in case db", ex);
767 scrollPane.setCursor(Cursor.DEFAULT);
771 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
772 "state", null, State.CREATE));
776 Platform.runLater(() -> {
777 imageTagCreator.disconnect();
778 masterGroup.getChildren().remove(imageTagCreator);
782 imageTagCreator.addNewTagListener(newTagListener);
783 Platform.runLater(() -> masterGroup.getChildren().add(imageTagCreator));
792 private ImageTag buildImageTag(ContentViewerTag<ImageTagRegion> contentViewerTag) {
793 ImageTag imageTag =
new ImageTag(contentViewerTag, fxImageView);
796 imageTag.subscribeToEditEvents((edit) -> {
798 scrollPane.setCursor(Cursor.WAIT);
799 ImageTagRegion newRegion = (ImageTagRegion) edit.getNewValue();
800 ContentViewerTagManager.updateTag(contentViewerTag, newRegion);
801 }
catch (SerializationException | TskCoreException | NoCurrentCaseException ex) {
802 LOGGER.log(Level.WARNING,
"Could not save edit for image tag in case db", ex);
804 scrollPane.setCursor(Cursor.DEFAULT);
816 private ContentViewerTag<ImageTagRegion> storeImageTag(ImageTagRegion data, TagNameAndComment result)
817 throws TskCoreException, SerializationException, NoCurrentCaseException {
818 scrollPane.setCursor(Cursor.WAIT);
820 ContentTag contentTag = Case.getCurrentCaseThrows().getServices().getTagsManager()
821 .addContentTag(file, result.getTagName(), result.getComment());
822 return ContentViewerTagManager.saveTag(contentTag, data);
824 scrollPane.setCursor(Cursor.DEFAULT);
832 private void showOrHideTags() {
833 Platform.runLater(() -> {
834 if (DisplayOptions.HIDE_TAGS.getName().equals(hideTagsMenuItem.getText())) {
836 masterGroup.getChildren().remove(tagsGroup);
837 hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
838 tagsGroup.clearFocus();
839 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
840 "state", null, State.HIDDEN));
843 masterGroup.getChildren().add(tagsGroup);
844 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
845 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
846 "state", null, State.VISIBLE));
852 "MediaViewImagePanel.exportSaveText=Save",
853 "MediaViewImagePanel.successfulExport=Tagged image was successfully saved.",
854 "MediaViewImagePanel.unsuccessfulExport=Unable to export tagged image to disk.",
855 "MediaViewImagePanel.fileChooserTitle=Choose a save location"
857 private void exportTags() {
858 tagsGroup.clearFocus();
859 exportChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
861 exportChooser.setCurrentDirectory(
new File(Case.getCurrentCase().getExportDirectory()));
862 int returnVal = exportChooser.showDialog(
this, Bundle.MediaViewImagePanel_exportSaveText());
863 if (returnVal == JFileChooser.APPROVE_OPTION) {
864 new SwingWorker<Void, Void>() {
866 protected Void doInBackground() {
869 List<ContentTag> tags = Case.getCurrentCase().getServices()
870 .getTagsManager().getContentTagsByContent(file);
871 List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
874 Collection<ImageTagRegion> regions = contentViewerTags.stream()
875 .map(cvTag -> cvTag.getDetails()).collect(Collectors.toList());
878 BufferedImage taggedImage = ImageTagsUtil.getImageWithTags(file, regions);
879 Path output = Paths.get(exportChooser.getSelectedFile().getPath(),
880 FilenameUtils.getBaseName(file.getName()) +
"-with_tags.png");
881 ImageIO.write(taggedImage,
"png", output.toFile());
883 JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport());
884 }
catch (Exception ex) {
886 LOGGER.log(Level.WARNING,
"Unable to export tagged image to disk", ex);
887 JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_unsuccessfulExport());
895 private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {
896 popupMenu.show(tagsMenu, -300 + tagsMenu.getWidth(), tagsMenu.getHeight() + 3);
902 enum DisplayOptions {
906 private final String name;
908 DisplayOptions(String name) {
933 private javax.swing.Box.Filler filler1;
934 private javax.swing.Box.Filler filler2;
935 private javax.swing.JPanel jPanel1;
936 private javax.swing.JToolBar.Separator jSeparator1;
937 private javax.swing.JToolBar.Separator jSeparator2;
938 private javax.swing.JButton rotateLeftButton;
939 private javax.swing.JButton rotateRightButton;
940 private javax.swing.JTextField rotationTextField;
941 private javax.swing.JButton tagsMenu;
942 private javax.swing.JToolBar toolbar;
943 private javax.swing.JButton zoomInButton;
944 private javax.swing.JButton zoomOutButton;
945 private javax.swing.JButton zoomResetButton;
946 private javax.swing.JTextField zoomTextField;
957 private void resetView() {
958 Image image = fxImageView.getImage();
963 double imageWidth = image.getWidth();
964 double imageHeight = image.getHeight();
965 double scrollPaneWidth = fxPanel.getWidth();
966 double scrollPaneHeight = fxPanel.getHeight();
967 double zoomRatioWidth = scrollPaneWidth / imageWidth;
968 double zoomRatioHeight = scrollPaneHeight / imageHeight;
971 zoomRatio = zoomRatioWidth < zoomRatioHeight ? zoomRatioWidth : zoomRatioHeight;
975 scrollPane.setHvalue(0);
976 scrollPane.setVvalue(0);
991 private void updateView() {
992 Image image = fxImageView.getImage();
998 double imageWidth = image.getWidth();
999 double imageHeight = image.getHeight();
1002 double adjustedImageWidth = imageWidth * zoomRatio;
1003 double adjustedImageHeight = imageHeight * zoomRatio;
1006 double viewportWidth;
1007 double viewportHeight;
1010 double panelWidth = fxPanel.getWidth();
1011 double panelHeight = fxPanel.getHeight();
1014 double centerOffsetX = (panelWidth / 2) - (imageWidth / 2);
1015 double centerOffsetY = (panelHeight / 2) - (imageHeight / 2);
1022 double scrollX = scrollPane.getHvalue();
1023 double scrollY = scrollPane.getVvalue();
1030 if ((rotation % 180) == 0) {
1032 viewportWidth = adjustedImageWidth;
1033 viewportHeight = adjustedImageHeight;
1034 leftOffsetX = (adjustedImageWidth - imageWidth) / 2;
1035 topOffsetY = (adjustedImageHeight - imageHeight) / 2;
1036 maxScrollX = (adjustedImageWidth - panelWidth) / (imageWidth - panelWidth);
1037 maxScrollY = (adjustedImageHeight - panelHeight) / (imageHeight - panelHeight);
1040 viewportWidth = adjustedImageHeight;
1041 viewportHeight = adjustedImageWidth;
1042 leftOffsetX = (adjustedImageHeight - imageWidth) / 2;
1043 topOffsetY = (adjustedImageWidth - imageHeight) / 2;
1044 maxScrollX = (adjustedImageHeight - panelWidth) / (imageWidth - panelWidth);
1045 maxScrollY = (adjustedImageWidth - panelHeight) / (imageHeight - panelHeight);
1049 if (viewportWidth < imageWidth) {
1050 viewportWidth = imageWidth;
1051 if (scrollX > maxScrollX) {
1052 scrollX = maxScrollX;
1055 if (viewportHeight < imageHeight) {
1056 viewportHeight = imageHeight;
1057 if (scrollY > maxScrollY) {
1058 scrollY = maxScrollY;
1063 fxImageView.setViewport(
new Rectangle2D(
1064 0, 0, viewportWidth, viewportHeight));
1067 Scale scale =
new Scale();
1068 scale.setX(zoomRatio);
1069 scale.setY(zoomRatio);
1070 scale.setPivotX(imageWidth / 2);
1071 scale.setPivotY(imageHeight / 2);
1074 Rotate rotate =
new Rotate();
1075 rotate.setPivotX(imageWidth / 2);
1076 rotate.setPivotY(imageHeight / 2);
1077 rotate.setAngle(rotation);
1080 Translate translate =
new Translate();
1081 translate.setX(viewportWidth > fxPanel.getWidth() ? leftOffsetX : centerOffsetX);
1082 translate.setY(viewportHeight > fxPanel.getHeight() ? topOffsetY : centerOffsetY);
1087 masterGroup.getTransforms().clear();
1088 masterGroup.getTransforms().addAll(translate, rotate, scale);
1091 if (viewportWidth > fxPanel.getWidth()) {
1092 scrollPane.setHvalue(scrollX);
1094 if (viewportHeight > fxPanel.getHeight()) {
1095 scrollPane.setVvalue(scrollY);
1099 zoomOutButton.setEnabled(zoomRatio > MIN_ZOOM_RATIO);
1100 zoomInButton.setEnabled(zoomRatio < MAX_ZOOM_RATIO);
1101 rotationTextField.setText((
int) rotation +
"°");
1102 zoomTextField.setText((Math.round(zoomRatio * 100.0)) +
"%");
static boolean isJavaFxInited()