package org.peerbox.presenter; import java.io.IOException; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import org.peerbox.guice.IFxmlLoaderProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.Singleton; /** * This navigation service offers simple page-based navigation similar to website navigation. * The service is responsible for loading pages and keeps track of the loaded content. * References of old pages are kept in a list in order to be able to navigate to * a previously loaded page (navigation backward). * * The service itself operates on a controller instance which is responsible for actually replacing * the content on a view element (e.g. content of a pane). * * Note: forward navigation is not supported (e.g. navigate back and then forward again to an * already loaded page is not possible) * * Important: the page list needs to be cleared if the history is not required anymore such that * they can be freed (garbage collection). The references are kept in the list until removed manually. * * @author albrecht * */ @Singleton public class NavigationService { private static final Logger logger = LoggerFactory.getLogger(NavigationService.class); /** * the controller of interest which is responsible for displaying and replacing content */ private INavigatable fController; /** * the content history */ private final ObservableList<Node> pages; /** * FXML loader supporting Google Guice (dependency injection), * loads pages and resolves dependencies of controllers using Guice */ private IFxmlLoaderProvider fxmlLoader; /** * Creates a new navigation service instance. * * @param loader the guice fxml loader to use (provides the injector instance) */ @Inject public NavigationService(IFxmlLoaderProvider loader) { if (loader == null) { throw new IllegalArgumentException("The argument loader must not be null."); } fxmlLoader = loader; pages = FXCollections.observableArrayList(); } /** * Sets the controller instance * * @param controller the instance to use for navigation */ public void setNavigationController(INavigatable controller) { fController = controller; } /** * Get the controller instance * * @return current controller instance */ public INavigatable getNavigationController() { return fController; } /** * Creates an FXML loader instance for an .fxml file supporting supporting DI * * @param fxmlFile the name of the resource to load * @return an FXML loader instance, ready to be used by calling the load() method * @throws IOException if loading fxml file fails. */ public FXMLLoader createLoader(final String fxmlFile) throws IOException { return fxmlLoader.create(fxmlFile); } /** * Navigates to a page by loading the page and creating a corresponding controller instance. * * @param fxmlFile the name of the page (resource) * @throws IllegalStateException if no controller is set */ public synchronized void navigate(final String fxmlFile) { if (fController == null) { throw new IllegalStateException("Controller must not be null, please set an instance."); } Node content = null; try { FXMLLoader loader = createLoader(fxmlFile); content = loader.load(); fController.setContent(content); pages.add(content); } catch (IOException e) { logger.warn("Could not load fxml file ({}).", e.getMessage(), e); } } /** * Navigate to the previous page. * Use canNavigateBack() to check whether there is a page that can be loaded. * * @throws IllegalStateException if there is no page to load. */ public synchronized void navigateBack() { if (canNavigateBack()) { pages.remove(pages.size() - 1); fController.setContent(pages.get(pages.size() - 1)); } else { logger.warn("Cannot go back (number of pages: {})", pages.size()); throw new IllegalStateException(String.format( "Cannot navigate back, number of pages: %s", pages.size())); } } /** * Indicates whether backward navigation is possible. * * @return true if there is a page that can be loaded. */ public synchronized boolean canNavigateBack() { return pages.size() >= 2; } /** * Clears the page history. */ public synchronized void clearPages() { pages.clear(); } }