/* * Autopsy Forensic Browser * * Copyright 2015-16 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.timeline; import java.io.IOException; import java.net.URL; import java.util.List; import java.util.logging.Level; import javafx.collections.FXCollections; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.Dialog; import javafx.scene.control.DialogPane; import javafx.scene.control.ListView; import javafx.scene.image.Image; import javafx.stage.Modality; import javafx.stage.Stage; import org.controlsfx.dialog.ProgressDialog; import org.controlsfx.tools.Borders; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Manager for the various prompts and dialogs Timeline shows the user related * to rebuilding the database. Methods must only be called on the JFX thread. */ public final class PromptDialogManager { private static final Logger LOGGER = Logger.getLogger(PromptDialogManager.class.getName()); @NbBundle.Messages("PrompDialogManager.buttonType.showTimeline=Continue") private static final ButtonType CONTINUE = new ButtonType(Bundle.PrompDialogManager_buttonType_showTimeline(), ButtonBar.ButtonData.OK_DONE); @NbBundle.Messages("PrompDialogManager.buttonType.continueNoUpdate=Continue Without Updating") private static final ButtonType CONTINUE_NO_UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_continueNoUpdate(), ButtonBar.ButtonData.CANCEL_CLOSE); @NbBundle.Messages("PrompDialogManager.buttonType.update=Update DB") private static final ButtonType UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_update(), ButtonBar.ButtonData.OK_DONE); /** * Image to use as title bar icon in dialogs */ private static final Image AUTOPSY_ICON; static { Image tempImg = null; try { tempImg = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); //NON-NLS } catch (IOException ex) { LOGGER.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); //NON-NLS } AUTOPSY_ICON = tempImg; } @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private Dialog<?> currentDialog; private final TimeLineController controller; /** * Constructor * * @param controller The TimeLineController this manager belongs to. */ PromptDialogManager(TimeLineController controller) { this.controller = controller; } /** * Bring the currently managed dialog (if there is one) to the front. * * @return True if a dialog was brought to the front, or false of there is * no currently managed open dialog */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean bringCurrentDialogToFront() { if (currentDialog != null && currentDialog.isShowing()) { ((Stage) currentDialog.getDialogPane().getScene().getWindow()).toFront(); return true; } return false; } /** * Show a progress dialog for the given db population task * * @param task The task to show progress for. */ @NbBundle.Messages({"PromptDialogManager.progressDialog.title=Populating Timeline Data"}) @ThreadConfined(type = ThreadConfined.ThreadType.JFX) void showDBPopulationProgressDialog(CancellationProgressTask<?> task) { currentDialog = new ProgressDialog(task); currentDialog.initModality(Modality.NONE); currentDialog.setTitle(Bundle.PromptDialogManager_progressDialog_title()); setDialogIcons(currentDialog); currentDialog.headerTextProperty().bind(task.titleProperty()); DialogPane dialogPane = currentDialog.getDialogPane(); dialogPane.setPrefSize(400, 200); //override autosizing which fails for some reason //co-ordinate task cancelation and dialog hiding. task.setOnCancelled(cancelled -> currentDialog.close()); task.setOnSucceeded(succeeded -> currentDialog.close()); task.setOnFailed(failed -> currentDialog.close()); dialogPane.getButtonTypes().setAll(ButtonType.CANCEL); final Node cancelButton = dialogPane.lookupButton(ButtonType.CANCEL); cancelButton.disableProperty().bind(task.cancellableProperty().not()); currentDialog.setOnCloseRequest(closeRequest -> { if (task.isRunning()) { closeRequest.consume(); } if (task.isCancellable() && task.isCancelRequested() == false) { task.requestCancel(); } }); currentDialog.show(); } /** * Set the title bar icon for the given dialog to be the autopsy logo icon. * * @param dialog The dialog to set the title bar icon for. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public static void setDialogIcons(Dialog<?> dialog) { ((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().setAll(AUTOPSY_ICON); } /** * Prompt the user that ingest is running and the DB may not end up * complete. * * @return True if they want to continue anyways. */ @NbBundle.Messages({ "PromptDialogManager.confirmDuringIngest.headerText=You are trying to update the Timeline DB before ingest has been completed. The Timeline DB may be incomplete.", "PromptDialogManager.confirmDuringIngest.contentText=Do you want to continue?"}) @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean confirmDuringIngest() { currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.PromptDialogManager_confirmDuringIngest_contentText(), CONTINUE, ButtonType.CANCEL); currentDialog.initModality(Modality.APPLICATION_MODAL); currentDialog.setTitle(Bundle.Timeline_dialogs_title()); setDialogIcons(currentDialog); currentDialog.setHeaderText(Bundle.PromptDialogManager_confirmDuringIngest_headerText()); //show dialog and map all results except "continue" to false. return currentDialog.showAndWait().map(CONTINUE::equals).orElse(false); } /** * Prompt the user to confirm rebuilding the database for the given list of * reasons. * * @param rebuildReasons A List of reasons why the database is out of date. * * @return True if the user a confirms rebuilding the database. */ @NbBundle.Messages({ "PromptDialogManager.rebuildPrompt.headerText=The Timeline DB is incomplete and/or out of date. Some events may be missing or inaccurate and some features may be unavailable.", "PromptDialogManager.rebuildPrompt.details=Details"}) @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean confirmRebuild(List<String> rebuildReasons) { currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.TimeLinecontroller_updateNowQuestion(), UPDATE, CONTINUE_NO_UPDATE); currentDialog.initModality(Modality.APPLICATION_MODAL); currentDialog.setTitle(Bundle.Timeline_dialogs_title()); setDialogIcons(currentDialog); currentDialog.setHeaderText(Bundle.PromptDialogManager_rebuildPrompt_headerText()); //set up listview of reasons to rebuild ListView<String> listView = new ListView<>(FXCollections.observableArrayList(rebuildReasons)); listView.setCellFactory(lstView -> new WrappingListCell()); listView.setMaxHeight(75); //wrap listview in title border. Node wrappedListView = Borders.wrap(listView) .lineBorder() .title(Bundle.PromptDialogManager_rebuildPrompt_details()) .buildAll(); DialogPane dialogPane = currentDialog.getDialogPane(); dialogPane.setExpandableContent(wrappedListView); dialogPane.setMaxWidth(500); //show dialog and map all results except "update" to false. return currentDialog.showAndWait().map(UPDATE::equals).orElse(false); } }