/* * Autopsy Forensic Browser * * Copyright 2013-15 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> 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.imagegallery.gui.drawableviews; import com.google.common.eventbus.Subscribe; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TitledPane; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.DataFormat; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import javafx.scene.text.Text; import javafx.util.Pair; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.datamodel.TagName; /** * Shows details of the selected file. */ @NbBundle.Messages({"MetaDataPane.tableView.placeholder=Select a file to show its details here.", "MetaDataPane.copyMenuItem.text=Copy", "MetaDataPane.titledPane.displayName=Details", "MetaDataPane.attributeColumn.headingName=Attribute", "MetaDataPane.valueColumn.headingName=Value"}) public class MetaDataPane extends DrawableUIBase { private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName()); private static final KeyCodeCombination COPY_KEY_COMBINATION = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_DOWN); @FXML private TitledPane titledPane; @FXML private TableColumn<Pair<DrawableAttribute<?>, Collection<?>>, DrawableAttribute<?>> attributeColumn; @FXML private TableView<Pair<DrawableAttribute<?>, Collection<?>>> tableView; @FXML private TableColumn<Pair<DrawableAttribute<?>, Collection<?>>, String> valueColumn; private final MenuItem copyMenuItem = new MenuItem(Bundle.MetaDataPane_copyMenuItem_text()); private final ContextMenu contextMenu = new ContextMenu(copyMenuItem); public MetaDataPane(ImageGalleryController controller) { super(controller); FXMLConstructor.construct(this, "MetaDataPane.fxml"); //NON-NLS } @FXML void initialize() { assert attributeColumn != null : "fx:id=\"attributeColumn\" was not injected: check your FXML file 'MetaDataPane.fxml'."; assert imageView != null : "fx:id=\"imageView\" was not injected: check your FXML file 'MetaDataPane.fxml'."; assert tableView != null : "fx:id=\"tableView\" was not injected: check your FXML file 'MetaDataPane.fxml'."; assert valueColumn != null : "fx:id=\"valueColumn\" was not injected: check your FXML file 'MetaDataPane.fxml'."; getController().getTagsManager().registerListener(this); getController().getCategoryManager().registerListener(this); //listen for selection change getController().getSelectionModel().lastSelectedProperty().addListener((observable, oldFileID, newFileID) -> { setFile(newFileID); }); copyMenuItem.setAccelerator(COPY_KEY_COMBINATION); copyMenuItem.setOnAction(actionEvent -> { copyValueToClipBoard(); }); tableView.setContextMenu(contextMenu); tableView.setOnKeyPressed((KeyEvent event) -> { if (COPY_KEY_COMBINATION.match(event)) { contextMenu.hide(); copyMenuItem.fire(); event.consume(); } }); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setPlaceholder(new Label(Bundle.MetaDataPane_tableView_placeholder())); tableView.getColumns().setAll(Arrays.asList(attributeColumn, valueColumn)); attributeColumn.setPrefWidth(USE_COMPUTED_SIZE); attributeColumn.setText(Bundle.MetaDataPane_attributeColumn_headingName()); attributeColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getKey())); attributeColumn.setCellFactory(param -> new TableCell<Pair<DrawableAttribute<?>, Collection<?>>, DrawableAttribute<?>>() { @Override protected void updateItem(DrawableAttribute<?> item, boolean empty) { super.updateItem(item, empty); if (isNull(item) || empty) { setGraphic(null); setText(null); } else { setText(item.getDisplayName()); setGraphic(new ImageView(item.getIcon())); } } }); valueColumn.setPrefWidth(USE_COMPUTED_SIZE); valueColumn.setText(Bundle.MetaDataPane_valueColumn_headingName()); valueColumn.setCellValueFactory(p -> new SimpleStringProperty(getValueDisplayString(p.getValue()))); valueColumn.setCellFactory(p -> new TableCell<Pair<DrawableAttribute<?>, Collection<?>>, String>() { @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (isNull(item) || empty) { setGraphic(null); } else { Text text = new Text(item); text.wrappingWidthProperty().bind(getTableColumn().widthProperty()); setGraphic(text); } } }); titledPane.setText(Bundle.MetaDataPane_titledPane_displayName()); } @SuppressWarnings("unchecked") static private String getValueDisplayString(Pair<DrawableAttribute<?>, Collection<?>> p) { if (p.getKey() == DrawableAttribute.TAGS) { return ((Collection<TagName>) p.getValue()).stream() .map(TagName::getDisplayName) .filter(Category::isNotCategoryName) .collect(Collectors.joining(" ; ")); } else { return p.getValue().stream() .map(value -> Objects.toString(value, "")) .collect(Collectors.joining(" ; ")); } } @Override synchronized protected void setFileHelper(Long newFileID) { setFileIDOpt(Optional.ofNullable(newFileID)); disposeContent(); if (nonNull(newFileID)) { updateAttributesTable(); updateCategory(); updateContent(); } } @Override protected synchronized void disposeContent() { super.disposeContent(); Platform.runLater(() -> { tableView.getItems().clear(); getCategoryBorderRegion().setBorder(null); }); } @Override Task<Image> newReadImageTask(DrawableFile file) { return file.getThumbnailTask(); } public void updateAttributesTable() { getFile().ifPresent(file -> { final List<Pair<DrawableAttribute<?>, Collection<?>>> attributesList = file.getAttributesList(); Platform.runLater(() -> { tableView.getItems().clear(); tableView.getItems().setAll(attributesList); }); }); } @Override public Region getCategoryBorderRegion() { return imageBorder; } /** * {@inheritDoc } */ @Subscribe @Override public void handleCategoryChanged(CategoryManager.CategoryChangeEvent evt) { getFileID().ifPresent(fileID -> { if (evt.getFileIDs().contains(fileID)) { updateCategory(); updateAttributesTable(); } }); } @Subscribe @Override public void handleTagAdded(ContentTagAddedEvent evt) { getFileID().ifPresent((fileID) -> { if (Objects.equals(evt.getAddedTag().getContent().getId(), fileID)) { updateAttributesTable(); } }); } @Override public void handleTagDeleted(ContentTagDeletedEvent evt) { getFileID().ifPresent((fileID) -> { if (Objects.equals(evt.getDeletedTagInfo().getContentID(), fileID)) { updateAttributesTable(); } }); } private void copyValueToClipBoard() { Pair<DrawableAttribute<?>, Collection<?>> selectedItem = tableView.getSelectionModel().getSelectedItem(); if (nonNull(selectedItem)) { Clipboard.getSystemClipboard().setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, getValueDisplayString(selectedItem))); } } }