/** * Copyright (c) 2014 Matthias Jaenicke <matthias.jaenicke@student.kit.edu>, * Matthias Plappert <undkc@student.kit.edu>, * Julien Duman <uncyc@student.kit.edu>, * Christian Dreher <uaeef@student.kit.edu>, * Wasilij Beskorovajnov <uajkm@student.kit.edu> and * Aydin Tekin <aydin.tekin@student.kit.edu> * * Released under the MIT license (refer to LICENSE.md) * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package edu.kit.iks.Cryptographics; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; import javax.swing.Timer; import edu.kit.iks.CryptographicsLib.AbstractController; import edu.kit.iks.CryptographicsLib.AbstractVisualizationController; import edu.kit.iks.CryptographicsLib.AbstractVisualizationInfo; import edu.kit.iks.CryptographicsLib.Configuration; import edu.kit.iks.CryptographicsLib.Logger; import edu.kit.iks.CryptographicsLib.MouseClickListener; /** * An instance of this class is a wrapper for visualization controller to * outsource common UI elements like navigation buttons * * @author Christian Dreher */ public class VisualizationContainerController extends AbstractController { /** * Index of the current {VisualizationController} in {childControllers}-list */ private int currentVisualizationControllerIndex; /** * List of all visualizationControllers. Behaves like a cache and will be * filled on demand */ private AbstractVisualizationController[] visualizationControllers; /** * {VisualizationInfo}-object holding metadata of the procuedure */ private AbstractVisualizationInfo visualizationInfo; /** * Container to display the actual visualization of a procedure */ private VisualizationContainerView view; /** * The timer used to detect an idle user. This is used to figure out when to * present the idle popover, so it's step 1. */ private Timer idleDetectionTimer; /** * The timer used to reset the state. This is step two of the idle detection. */ private Timer resetTimer; /** * The global event listener used for detecting an idle user. */ private AWTEventListener idleDetectionListener; /** * The popover presented to warn the user before going back to the main screen. */ private IdlePopoverView idlePopoverView; /** * List of all child classes */ @SuppressWarnings("rawtypes") List<Class> childClasses; /** * View of the popover wich shows help for the user. */ private HelpPopoverView helpPopoverView; /** * Constructor initializing a new instance of * {VisualizationContainerController} * * @param visualizationInfo * Object of {VisualizationInfo} containing the data to * instantiate related controllers from */ public VisualizationContainerController( AbstractVisualizationInfo visualizationInfo) { this.visualizationInfo = visualizationInfo; this.childClasses = this.visualizationInfo.getControllerClasses(); this.idleDetectionListener = new AWTEventListener() { @Override public void eventDispatched(AWTEvent e) { stopIdleDetectionTimer(); if (idlePopoverView == null) { startIdleDetectionTimer(); } } }; this.startIdleDetectionTimer(); // Create an array that can hold all visualization controllers. // The controller's will be instantiated on demand (lazily). this.visualizationControllers = new AbstractVisualizationController[this.childClasses .size()]; } /** * Gets the {VisualizationInfo}-object of the current visualization * * @return {VizualizationInfo}-object of the current visualization */ public AbstractVisualizationInfo getVisualizationInfo() { return this.visualizationInfo; } /** * Loads the view */ @Override public void loadView() { // Observe global mouse events. Toolkit.getDefaultToolkit().addAWTEventListener(this.idleDetectionListener, AWTEvent.MOUSE_MOTION_EVENT_MASK); this.view = new VisualizationContainerView(); this.view.setName("visualization-container-controller-view"); // Styling purpose this.view.setName("visualizationContainerController"); this.view.getNameLabel().setText("<html><h1>" + this.getVisualizationInfo().getName() + "</h1></html>"); this.view.getExitButton().addMouseListener(new MouseClickListener() { @Override public void clicked(MouseEvent e) { MainController mainController = (MainController) getParentController(); getCurrentVisualizationController().unloadView(); Logger.log("User went back to start screen"); mainController.presentStartAction(); } }); this.view.getHelpButton().addMouseListener(new MouseClickListener() { @Override public void clicked(MouseEvent e) { presentHelpPopover(); } }); } /** * Unloads the view */ @Override public void unloadView() { // Remove observer. Toolkit.getDefaultToolkit().removeAWTEventListener(this.idleDetectionListener); // Stop timers. this.stopIdleDetectionTimer(); this.stopResetTimer(); if (this.helpPopoverView != null) { this.dismissHelpPopover(); } if (this.idlePopoverView != null) { this.dismissIdlePopover(); } this.view.removeAll(); this.view.revalidate(); this.helpPopoverView = null; this.idlePopoverView = null; this.view = null; } /** * Gets the view */ @Override public VisualizationContainerView getView() { return this.view; } /** * Creates and presents a new idle popover. The idle popover is presented * after the program things that the user is idle. It displays a count-down * after which it resets itself. */ public void presentIdlePopover() { if (this.helpPopoverView != null) { this.dismissHelpPopover(); } if (this.idlePopoverView != null) { this.dismissIdlePopover(); } // Create popover. this.idlePopoverView = new IdlePopoverView(Configuration.getInstance().getResetTimeout()); this.idlePopoverView.present(this.getView().getExitButton()); // Create mouse listeners for buttons. MouseClickListener listener = new MouseClickListener() { @Override public void clicked(MouseEvent e) { dismissIdlePopover(); startIdleDetectionTimer(); } }; this.idlePopoverView.getCloseButton().addMouseListener(listener); this.idlePopoverView.getContinueButton().addMouseListener(listener); this.startResetTimer(); } /** * Dismisses the active idle popover. */ public void dismissIdlePopover() { this.idlePopoverView.dismiss(); this.idlePopoverView = null; this.stopResetTimer(); } /** * Stops the reset timer. */ private void stopResetTimer() { if (this.resetTimer != null) { this.resetTimer.stop(); this.resetTimer = null; } } /** * Starts the reset timer. After the timer fires, the program * will present the start controller. */ private void startResetTimer() { if (this.resetTimer == null) { int delay = Configuration.getInstance().getResetTimeout(); this.resetTimer = new Timer(delay, new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { stopResetTimer(); MainController mainController = (MainController) getParentController(); mainController.presentStartAction(); Logger.log("Reset due to user inactivity"); } }); this.resetTimer.start(); } } /** * Stops an idle detection timer. */ private void stopIdleDetectionTimer() { if (this.idleDetectionTimer != null) { this.idleDetectionTimer.stop(); this.idleDetectionTimer = null; } } /** * Starts the idle detection timer. The idle detection fires if a user * doesn't perform any input for a given period of time. */ private void startIdleDetectionTimer() { if (this.idleDetectionTimer == null) { // Create a new timer if the idle popover is currently not already active. int delay = Configuration.getInstance().getIdleTimeout(); this.idleDetectionTimer = new Timer(delay, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { stopIdleDetectionTimer(); presentIdlePopover(); } }); this.idleDetectionTimer.start(); } } /** * Shows a popover displaying given help. * */ public void presentHelpPopover() { if (this.helpPopoverView != null) { this.dismissHelpPopover(); } if (this.idlePopoverView != null) { this.dismissIdlePopover(); } String helpText = this.getCurrentVisualizationController().getHelp(); if (helpText == null) { // Do not present help if no help is available. return; } this.helpPopoverView = new HelpPopoverView(helpText); this.helpPopoverView.getCloseButton().addMouseListener(new MouseClickListener() { @Override public void clicked(MouseEvent e) { dismissHelpPopover(); } }); this.helpPopoverView.present(this.getView().getHelpButton()); } /** * Function for unloading the helpView. */ public void dismissHelpPopover() { this.helpPopoverView.dismiss(); this.helpPopoverView = null; } /** * Sets the current visualizationController with given index * * @param index * Index of the VisualizationController inside * {visualizationControllers}-array */ public void setCurrentVisualizationControllerIndex(int index) { AbstractVisualizationController oldController = this .getCurrentVisualizationController(); if (oldController != null) { this.removeChildController(oldController); // Remove view this.getView().getContentView().remove(oldController.getView()); oldController.unloadView(); } // Load new controller if necessary. AbstractVisualizationController newController = this.visualizationControllers[index]; if (newController == null) { newController = this.loadVisualizationController(index); } // Set new controller this.currentVisualizationControllerIndex = index; this.addChildController(newController); // Load new view newController.loadView(); this.getView().getContentView().add(newController.getView(), BorderLayout.CENTER); this.getView().revalidate(); } /** * Gets the current {VisualizationController} * * @return Current {VisualizationController} */ public AbstractVisualizationController getCurrentVisualizationController() { return this.visualizationControllers[this.currentVisualizationControllerIndex]; } /** * Checks, whether this controller has a next {VisualizationController} or * not * * @return {true}, if this controller has a next {VisualizationController}, * {false} if not */ public boolean hasNextVisualizationController() { return (this.currentVisualizationControllerIndex < childClasses.size() - 1); } /** * Presents the next {VisualizationController} */ public void presentNextVisualizationController() { this.setCurrentVisualizationControllerIndex(this.currentVisualizationControllerIndex + 1); } /** * Checks, whether this controller has a previous {VisualizationController} * or not * * @return {true}, if this controller has a previous * {VisualizationController}, {false} if not */ public boolean hasPreviousVisualizationController() { return (this.currentVisualizationControllerIndex > 1); } /** * Presents the previous {VisualizationController} */ public void presentPreviousVisualizationController() { this.setCurrentVisualizationControllerIndex(this.currentVisualizationControllerIndex - 1); } /** * Loads the {VisualizationController} with given {index} * * @param index * Index of the {VisualizationController} inside * {visualizationControllers}-array * @return The just loaded {VisualizationController} */ private AbstractVisualizationController loadVisualizationController( int index) { Constructor<AbstractVisualizationController> controllerConstructor = null; @SuppressWarnings("unchecked") Class<AbstractVisualizationController> controllerClass = childClasses .get(index); AbstractVisualizationController controller = null; try { // visualizationinfo is now passed to the contructor. controllerConstructor = controllerClass .getConstructor(AbstractVisualizationInfo.class); controller = controllerConstructor .newInstance(this.visualizationInfo); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { Logger.error(e); } this.visualizationControllers[index] = controller; return controller; } /** * Presents the start controller */ public void presentStartController() { if (this.helpPopoverView != null) { this.dismissHelpPopover(); } MainController mainController = (MainController) this .getParentController(); mainController.presentStartAction(); } /** * @return the helpView */ public HelpPopoverView getHelpPopoverView() { return helpPopoverView; } }