All Downloads are FREE. Search and download functionalities are using the official Maven repository.

application.MainController Maven / Gradle / Ivy

package application;

import java.awt.Desktop;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.daisy.braille.utils.api.table.TableCatalog;
import org.daisy.braille.utils.pef.TextConverterFacade;
import org.daisy.dotify.api.tasks.AnnotatedFile;
import org.daisy.dotify.consumer.identity.IdentityProvider;
import org.daisy.dotify.consumer.tasks.TaskGroupFactoryMaker;
import org.daisy.dotify.studio.api.Editor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.googlecode.e2u.StartupDetails;
import com.googlecode.e2u.StartupDetails.Mode;

import application.about.AboutView;
import application.imports.ImportBrailleView;
import application.l10n.Messages;
import application.prefs.PreferencesView;
import application.preview.EditorController;
import application.preview.PreviewController;
import application.preview.SourcePreviewController;
import application.search.SearchController;
import application.template.TemplateView;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.control.SplitPane;
import javafx.scene.control.SplitPane.Divider;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.Dragboard;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import shared.Settings;

/**
 * Provides the controller for the main view.
 * @author Joel Håkansson
 *
 */
public class MainController {
	private static final Logger logger = Logger.getLogger(MainController.class.getCanonicalName());
	private static final int CONSOLE_MESSAGE_LIMIT = 500;
	@FXML private BorderPane root;
	@FXML private TabPane tabPane;
	@FXML private TabPane toolsPane;
	@FXML private SplitPane splitPane;
	@FXML private SplitPane verticalSplitPane;
	@FXML private BorderPane consoleRoot;
	@FXML private WebView console;
	@FXML private ScrollPane consoleScroll;
	@FXML private MenuItem closeMenuItem;
	@FXML private MenuItem exportMenuItem;
	@FXML private MenuItem saveMenuItem;
	@FXML private MenuItem saveAsMenuItem;
	@FXML private MenuItem refreshMenuItem;
	@FXML private MenuItem openInBrowserMenuItem;
	@FXML private MenuItem embossMenuItem;
	@FXML private CheckMenuItem showSearchMenuItem;
	@FXML private CheckMenuItem showConsoleMenuItem;
	@FXML private ToggleButton scrollLockButton;
	@FXML private MenuItem nextEditorViewMenuItem;
	@FXML private MenuItem previousEditorViewMenuItem;
	@FXML private MenuItem toggleViewMenuItem;
	@FXML private MenuItem activateViewMenuItem;
	private final double dividerPosition = 0.2;
	private Tab searchTab;
	private Tab helpTab;
	private ExecutorService exeService;
	private double[] verticalDividerPositions;
	private BooleanProperty canEmboss;
	private BooleanProperty canExport;
	private BooleanProperty canSave;
	private BooleanProperty canToggleView;
	private StringProperty urlProperty;
	static final KeyCombination CTRL_F4 = new KeyCodeCombination(KeyCode.F4, KeyCombination.CONTROL_DOWN);

	@FXML void initialize() {
		toolsPane.addEventHandler(KeyEvent.KEY_RELEASED, ev-> {
			if (CTRL_F4.match(ev)) {
				Tab t = toolsPane.getSelectionModel().getSelectedItem();
				if (t!=null) {
					toolsPane.getTabs().remove(t);
				}
				adjustToolsArea();
				ev.consume();
			}
		});
		tabPane.addEventHandler(KeyEvent.KEY_RELEASED, ev -> {
			if (CTRL_F4.match(ev)) {
				Tab t = tabPane.getSelectionModel().getSelectedItem();
				if (t!=null) {
					// TODO: don't remove the tab directly, ask it to close. 
					// See https://github.com/brailleapps/dotify-studio/issues/48
					// Event.fireEvent(t, new Event(Tab.TAB_CLOSE_REQUEST_EVENT));
					tabPane.getTabs().remove(t);
				}
				ev.consume();
			}
		});
		root.setOnDragOver(event->{
			Dragboard db = event.getDragboard();
			if (db.hasFiles() && canDropFiles(db.getFiles())) {
				event.acceptTransferModes(TransferMode.COPY);
			} else {
				event.consume();
			}
		});
		root.setOnDragDropped(event -> {
			Dragboard db = event.getDragboard();
			boolean success = false;
			if (db.hasFiles()) {
				success = true;
				for (File file : db.getFiles()) {
					Platform.runLater(() -> {
						if (file.getName().endsWith(".pef")) {
							addTab(file);
						} else {
							selectTemplateAndOpen(file);
						}
					});
				}
			}
			event.setDropCompleted(success);
			event.consume();
		});

		clearConsole();
		
		console.setOnDragOver(event->event.consume());
		exeService = Executors.newWorkStealingPool();
		System.setOut(new PrintStream(new ConsoleStream("out")));
		System.setErr(new PrintStream(new ConsoleStream("err")));
		try {
			//this is done to reinitialize the console logger so that it redirects to the new print streams set above
			LogManager.getLogManager().readConfiguration();
		} catch (SecurityException | IOException e) {
			e.printStackTrace();
		}
		canEmboss = new SimpleBooleanProperty();
		canExport = new SimpleBooleanProperty();
		canSave = new SimpleBooleanProperty();
		canToggleView = new SimpleBooleanProperty();
		urlProperty = new SimpleStringProperty();
		//add menu bindings
		setMenuBindings();
	}
	
	private void setMenuBindings() {
		tabPane.getSelectionModel().selectedItemProperty().addListener((o, ov, nv)->{
			canEmboss.unbind();
			canExport.unbind();
			canSave.unbind();
			canToggleView.unbind();
			urlProperty.unbind();
			if (nv!=null && nv.getContent() instanceof Editor) {
				Editor p = ((Editor)nv.getContent());
				canEmboss.bind(p.canEmbossProperty());
				canExport.bind(p.canExportProperty());
				canSave.bind(p.canSaveProperty());
				canToggleView.bind(p.toggleViewProperty());
				urlProperty.bind(p.urlProperty());
			} else {
				canEmboss.set(false);
				canExport.set(false);
				canSave.set(false);
				canToggleView.set(false);
				urlProperty.set(null);
			}
		});
		BooleanBinding noTabBinding = tabPane.getSelectionModel().selectedItemProperty().isNull();
		BooleanBinding noTabExceptHelpBinding = noTabBinding.or(
				tabPane.getSelectionModel().selectedItemProperty().isEqualTo(helpTab)
		);
		closeMenuItem.disableProperty().bind(noTabBinding);
		exportMenuItem.disableProperty().bind(noTabExceptHelpBinding.or(canExport.not()));
		saveMenuItem.disableProperty().bind(noTabExceptHelpBinding.or(canSave.not()));
		nextEditorViewMenuItem.disableProperty().bind(noTabBinding.or(
					Bindings.size(tabPane.getTabs()).lessThan(2))
				);
		previousEditorViewMenuItem.disableProperty().bind(noTabBinding.or(
					Bindings.size(tabPane.getTabs()).lessThan(2))
				);
		toggleViewMenuItem.disableProperty().bind(noTabExceptHelpBinding.or(canToggleView.not()));
		activateViewMenuItem.disableProperty().bind(noTabExceptHelpBinding);
		saveAsMenuItem.disableProperty().bind(noTabExceptHelpBinding);
		refreshMenuItem.disableProperty().bind(noTabBinding);
		openInBrowserMenuItem.disableProperty().bind(noTabBinding.or(urlProperty.isNull()));
		embossMenuItem.disableProperty().bind(noTabExceptHelpBinding.or(canEmboss.not()));
	}
	
	private static boolean canDropFiles(List files) {
		List exts = newImportExtensionStream()
			.map(val -> "."+val.toLowerCase())
			.collect(Collectors.toList());
		for (File f : files) {
			if (!(f.getName().endsWith(".pef")||exts.contains(getExtension(f.getName().toLowerCase())))) {
				return false;
			}
		}
		return true;
	}
	
	private static String getExtension(String filename) {
		int ix = filename.lastIndexOf('.');
		if (ix<0) {
			return "";
		} else {
			return filename.substring(ix);
		}
	}
	
	@FXML void clearConsole() {
		synchronized (console) {
			console.getEngine().loadContent("");
			console.getEngine().setUserStyleSheetLocation(this.getClass().getResource("resource-files/console.css").toString());
		}
	}
	
	@FXML void showHideConsole() {
		if (showConsoleMenuItem.isSelected()) {
			verticalSplitPane.getItems().add(consoleRoot);
			verticalSplitPane.setDividerPositions(verticalDividerPositions);
		} else {
			verticalDividerPositions = verticalSplitPane.getDividerPositions();
			verticalSplitPane.getItems().remove(consoleRoot);
		}
	}

	private class ConsoleStream extends OutputStream {
		ByteArrayOutputStream bytes;
		private final String name;
		
		ConsoleStream(String name) {
			Objects.requireNonNull(name);
			this.bytes = new ByteArrayOutputStream();
			this.name = name;
		}
		
		void write(String s) {
			Platform.runLater(()->{
				synchronized (console) {
					Document doc = console.getEngine().getDocument();
					org.w3c.dom.Node body = doc.getElementsByTagName("body").item(0);
					Element p = doc.createElement("p");
					p.setAttribute("class", name);
					p.appendChild(doc.createTextNode(s));
					body.appendChild(p);
					while (body.getChildNodes().getLength()>CONSOLE_MESSAGE_LIMIT) {
						body.removeChild(body.getChildNodes().item(0));
					}
					if (!scrollLockButton.isSelected()) {
						console.getEngine().executeScript("window.scrollTo(0, document.body.scrollHeight);");
					}
				}
			});
		}
		
		@Override
		public void write(int b) throws IOException {
			if (b=='\n') {
				bytes.write('\n');
				write(new String(bytes.toByteArray()));
				bytes.reset();
			} else if (b!='\r') {
				bytes.write(b);
			}
		}
	}
	
	void openArgs(StartupDetails args) {
        if (args.getMode()!=Mode.UNDEFINED) {
        	String title = null;
        	if (args.getMode()==Mode.OPEN) {
        		title = args.getFile().getName();
        	}
        	addTab(title, args);
        }
	}
	
	private Optional getSelectedPreview() {
		return Optional.ofNullable(tabPane.getSelectionModel().getSelectedItem())
				.map(t->t.getContent())
				.filter(n->(n instanceof Editor))
				.map(n->(Editor)n);
	}
	
	@FXML void toggleEditor() {
		getSelectedPreview().ifPresent(p->p.toggleView());
	}
	
	@FXML void nextEditor() {
		SingleSelectionModel select = tabPane.getSelectionModel();
		if (select.getSelectedIndex()>=tabPane.getTabs().size()-1) {
			select.selectFirst();
		} else {
			select.selectNext();
		}
	}
	
	@FXML void previousEditor() {
		SingleSelectionModel select = tabPane.getSelectionModel();
		if (select.getSelectedIndex()<1) {
			select.selectLast();
		} else {
			tabPane.getSelectionModel().selectPrevious();
		}
	}
	
	@FXML void activateView() {
		getSelectedPreview().ifPresent(v->v.activate());
	}

    @FXML void refresh() {
		Tab t = tabPane.getSelectionModel().getSelectedItem();
		if (t!=null) {
			javafx.scene.Node node = t.getContent();
			if (node instanceof WebView) {
				((WebView) node).getEngine().reload();
			} else if (node instanceof Editor) {
				((Editor)node).reload();
			}
		}
    }
    
    @FXML void openInBrowser() {
		if (Desktop.isDesktopSupported()) {
			try {
				Tab t = tabPane.getSelectionModel().getSelectedItem();
				if (t!=null) {
					javafx.scene.Node node = t.getContent();
					if (node instanceof WebView) {
						Desktop.getDesktop().browse(new URI(((WebView) node).getEngine().getLocation()));
					} else if (node instanceof Editor) {
						((Editor)t.getContent()).getURL().ifPresent(url->{
							try {
								Desktop.getDesktop().browse(new URI(url));
							} catch (IOException | URISyntaxException e) {
								// fail silently
							}
						});
					}
				}				
			} catch (IOException | URISyntaxException e) {
			}
		}
	}
    
    @FXML void emboss() {
    	Tab t = tabPane.getSelectionModel().getSelectedItem();
		if (t!=null) {
			Editor controller = ((Editor)t.getContent());
			if (controller.canEmboss()) {
				Platform.runLater(()->{
					controller.showEmbossDialog();
				});
			}
		}
    }
    
    @FXML void closeTab() {
    	Tab t = tabPane.getSelectionModel().getSelectedItem();
		if (t!=null) {
			tabPane.getTabs().remove(t);
		}
    }
    
    @FXML void save() {
    	Tab t = tabPane.getSelectionModel().getSelectedItem();
		if (t!=null) {
			Editor controller = ((Editor)t.getContent());
			if (controller.canSave()) {
				controller.save();
			}
		}
    }
    
    @FXML void saveAs() {
    	Tab t = tabPane.getSelectionModel().getSelectedItem();
		if (t!=null) {
			Editor controller = ((Editor)t.getContent());
			// display save dialog
			Window stage = root.getScene().getWindow();
	    	FileChooser fileChooser = new FileChooser();
	    	fileChooser.setTitle(Messages.TITLE_SAVE_AS_DIALOG.localize());
	    	fileChooser.getExtensionFilters().addAll(controller.getSaveAsFilters());
	    	Settings.getSettings().getLastSavePath().ifPresent(v->fileChooser.setInitialDirectory(v));
	    	File selected = fileChooser.showSaveDialog(stage);
	    	if (selected!=null) {
	    		Settings.getSettings().setLastSavePath(selected.getParentFile());
				try {
					controller.saveAs(selected);
					// stop watching
					controller.closing();
					// update contents of tab
					setTab(t, selected.getName(), StartupDetails.open(selected), controller.getOptions());
					// TODO: Restore document position
				} catch (IOException e) {
					Alert alert = new Alert(AlertType.ERROR, e.getMessage(), ButtonType.OK);
		    		alert.showAndWait();
				}
	    	}
		}
    }
    
    @FXML void toggleSearchArea() {
    	ObservableList dividers = splitPane.getDividers();
    	//TODO: observe changes and restore to that value
    	if (dividers.get(0).getPosition()>dividerPosition/2) {
    		splitPane.setDividerPosition(0, 0);
    		/*
    		expandButton.setText(">");
    		listView.setVisible(false);
    		searchFor.setVisible(false);*/
    	} else {
    		//expandButton.setText("<");
    		splitPane.setDividerPosition(0, dividerPosition);
    		/*
    		listView.setVisible(true);
    		searchFor.setVisible(true);*/
    	}
    }
    
    @FXML void openPreferences() {
		PreferencesView dialog = new PreferencesView();
		dialog.initOwner(root.getScene().getWindow());
		dialog.initModality(Modality.APPLICATION_MODAL);
		dialog.showAndWait();
		File generated = dialog.generatedTestFile();
		if (generated!=null) {
			addTab(generated.getName(), StartupDetails.open(generated));
		}
    }
    
    @FXML void openAbout() {
		AboutView dialog = new AboutView();
		dialog.initOwner(root.getScene().getWindow());
		dialog.initModality(Modality.APPLICATION_MODAL); 
		dialog.showAndWait();
    }
    
    @FXML void showHideSearch() {
		if (showSearchMenuItem.isSelected()) {
			if (searchTab==null || !toolsPane.getTabs().contains(searchTab)) {
				SearchController controller = new SearchController();
				controller.setOnOpen(book -> addTab(new File(book.getBook().getURI())));
				searchTab = addTabToTools(controller, Messages.TAB_SEARCH.localize());
			} else {
				//focus
				toolsPane.getSelectionModel().select(searchTab);
				if (splitPane.getDividerPositions()[0] {
        	adjustToolsArea();
        });
        if (toolsPane.getTabs().size()==0) {
    		splitPane.setDividerPosition(0, dividerPosition);
    	}
        toolsPane.getTabs().add(tab);
        toolsPane.getSelectionModel().select(tab);
        return tab;
    }
    
    private void adjustToolsArea() {
    	if (toolsPane.getTabs().size()==0) {
    		splitPane.setDividerPosition(0, 0);
    	}
    	showSearchMenuItem.setSelected(toolsPane.getTabs().contains(searchTab));
    }
    
    @FXML void showOpenDialog() {
    	Window stage = root.getScene().getWindow();
    	FileChooser fileChooser = new FileChooser();
    	fileChooser.setTitle(Messages.TITLE_OPEN_DIALOG.localize());
    	fileChooser.getExtensionFilters().add(new ExtensionFilter("PEF-files", "*.pef"));
		if (FeatureSwitch.OPEN_OTHER_TYPES.isOn()) {
			fileChooser.getExtensionFilters().add(new ExtensionFilter("XML-files", "*.xml"));
			fileChooser.getExtensionFilters().add(new ExtensionFilter("Text-files", "*.txt"));
		}
    	Settings.getSettings().getLastOpenPath().ifPresent(v->fileChooser.setInitialDirectory(v));
    	File selected = fileChooser.showOpenDialog(stage);
    	if (selected!=null) {
    		Settings.getSettings().setLastOpenPath(selected.getParentFile());
    		addTab(selected);
    	}
    }

    @FXML void showImportBrailleDialog() {
    	Window stage = root.getScene().getWindow();
    	FileChooser fileChooser = new FileChooser();
    	fileChooser.setTitle(Messages.TITLE_IMPORT_BRAILLE_TEXT_DIALOG.localize());
    	File selected = fileChooser.showOpenDialog(stage);
    	if (selected!=null) {
    		ImportBrailleView brailleView = new ImportBrailleView(selected);
    		brailleView.initOwner(root.getScene().getWindow());
    		brailleView.initModality(Modality.APPLICATION_MODAL);
    		brailleView.showAndWait();
    		Map settings = brailleView.getOptions();
    		if (settings!=null) {
				try {
					File output = File.createTempFile(selected.getName(), ".pef");
					output.deleteOnExit();
			    	Task importTask = new Task(){
						@Override
						protected Void call() throws Exception {
				    		TextConverterFacade f = new TextConverterFacade(TableCatalog.newInstance());
				    		f.parseTextFile(selected, output, settings);
							return null;
						}
			    	};
			    	importTask.setOnFailed(e->{
			    		logger.log(Level.WARNING, "Import failed.", importTask.getException());
			    		Alert alert = new Alert(AlertType.ERROR, importTask.getException().toString(), ButtonType.OK);
			    		alert.showAndWait();
			    	});
			    	importTask.setOnSucceeded(e->{
			    		Platform.runLater(()->{
			    			addTab(output);
			    		});
			    	});
			    	exeService.submit(importTask);
				} catch (IOException e1) {
					logger.log(Level.WARNING, "Failed to create temporary file.", e1);
				}
    		}
    	}
    }
    
    /**
     * Returns a new stream with unique input formats.
     * @return
     */
    private static Stream newImportExtensionStream() {
    	return TaskGroupFactoryMaker.newInstance().listAll().stream()
			.filter(spec ->
				// Currently, this can be viewed as an identity conversion, which isn't supported by the task system.
				// TODO: Perhaps support this as a special case in this code instead (just open the file without going through the task system).
				!"pef".equals(spec.getInputFormat())
				// Not all formats in this list are actually extensions.
				// TODO: Filter these out here until the TaskGroupFactory provides extensions separately. 
				&& !"dtbook".equals(spec.getInputFormat()) // use xml instead
				&& !"text".equals(spec.getInputFormat())) // use txt instead
			.map(spec -> spec.getInputFormat())
			.distinct();
    }

    @FXML void showImportDialog() {
    	Window stage = root.getScene().getWindow();
    	FileChooser fileChooser = new FileChooser();
    	fileChooser.setTitle(Messages.TITLE_IMPORT_SOURCE_DOCUMENT_DIALOG.localize());
		List exts = newImportExtensionStream()
				.map(val -> "*."+val)
				.collect(Collectors.toList()); 
		fileChooser.getExtensionFilters().add(new ExtensionFilter(Messages.EXTENSION_FILTER_SUPPORTED_FILES.localize(), exts));
		// All extensions are individually as well
		// TODO: additional information from the TaskGroupFactory about the formats (i.e. descriptions) would be useful for this list
		fileChooser.getExtensionFilters().addAll(
				exts.stream()
				.map(ext->new ExtensionFilter(ext, ext))
				.collect(Collectors.toList())
				);
		fileChooser.getExtensionFilters().add(new ExtensionFilter(Messages.EXTENSION_FILTER_ALL_FILES.localize(), "*.*"));
		Settings.getSettings().getLastOpenPath().ifPresent(v->fileChooser.setInitialDirectory(v));
    	File selected = fileChooser.showOpenDialog(stage);
    	if (selected!=null) {
    		Settings.getSettings().setLastOpenPath(selected.getParentFile());
    		selectTemplateAndOpen(selected);
    	}
    }
    
    private void selectTemplateAndOpen(File selected) {
		// choose template
		TemplateView dialog = new TemplateView(selected);
		if (dialog.hasTemplates()) {
			dialog.initOwner(root.getScene().getWindow());
			dialog.initModality(Modality.APPLICATION_MODAL); 
			dialog.showAndWait();
		}

		// convert then add tab
		addSourceTab(selected, dialog.getSelectedConfiguration());
    }
    
    @FXML void exportFile() {
    	Tab t = tabPane.getSelectionModel().getSelectedItem();
		if (t!=null) {
			Editor controller = ((Editor)t.getContent());
			if (controller.canExport()) {
		    	Window stage = root.getScene().getWindow();
		    	FileChooser fileChooser = new FileChooser();
		    	fileChooser.setTitle(Messages.TITLE_EXPORT_DIALOG.localize());
		    	Settings.getSettings().getLastSavePath().ifPresent(v->fileChooser.setInitialDirectory(v));
		    	File selected = fileChooser.showSaveDialog(stage);
		    	if (selected!=null) {
		    		Settings.getSettings().setLastSavePath(selected.getParentFile());
			    	Task exportTask = new Task() {
						@Override
						protected Void call() throws Exception {
							controller.export(selected);
							return null;
						}
			    	};
			    	exportTask.setOnFailed(e->{
			    		exportTask.getException().printStackTrace();
			    		Alert alert = new Alert(AlertType.ERROR, exportTask.getException().toString(), ButtonType.OK);
			    		alert.showAndWait();
			    	});
			    	exeService.submit(exportTask);
				}	
	    	}
		}
    }

	@FXML void closeApplication() {
		Stage stage = ((Stage)root.getScene().getWindow());
		stage.fireEvent(new WindowEvent(stage, WindowEvent.WINDOW_CLOSE_REQUEST));
	}

	@FXML void openHelpTab() {
		// if the tab is not visible, recreate it (this is a workaround for https://github.com/brailleapps/dotify-studio/issues/20)
		Tab old = helpTab;
		if (helpTab==null || !tabPane.getTabs().contains(helpTab)) {
			WebView wv = new WebView();
			wv.setOnDragOver(event->event.consume());
			helpTab = new Tab(Messages.TAB_HELP_CONTENTS.localize(), wv);
			helpTab.setGraphic(buildImage(this.getClass().getResource("resource-files/help.png")));
			String helpURL = getHelpURL();
			if (helpURL!=null) {
				WebEngine engine = wv.getEngine();
				engine.load(helpURL);
			} else {
				wv.getEngine().loadContent("

"+Messages.ERROR_FAILED_TO_LOAD_HELP.localize()+"

"); } } if (!tabPane.getTabs().contains(helpTab)) { tabPane.getTabs().add(helpTab); } tabPane.getSelectionModel().select(helpTab); if (helpTab!=old) { setMenuBindings(); } } boolean confirmShutdown() { if (isAnyModified()) { Alert alert = new Alert(AlertType.CONFIRMATION, Messages.MESSAGE_CONFIRM_QUIT_UNSAVED_CHANGES.localize(), ButtonType.YES, ButtonType.CANCEL); Optional res = alert.showAndWait(); return res.map(v->(Boolean)v.equals(ButtonType.YES)).orElse(false); } else { return true; } } private boolean isAnyModified() { return tabPane.getTabs().stream() .map(t->t.getContent()) .filter(n->(n instanceof Editor)) .map(n->(Editor)n) .anyMatch(e->e.isModified()); } private static ImageView buildImage(URL url) { Image i = new Image(url.toString()); ImageView imageView = new ImageView(); imageView.setImage(i); return imageView; } private String getHelpURL() { try { File codeLocation = new File((this.getClass().getProtectionDomain().getCodeSource().getLocation()).toURI()).getParentFile(); File root = null; if (codeLocation.getName().equalsIgnoreCase("lib")) { root = codeLocation.getParentFile(); } else { root = codeLocation; } File docs = new File(new File(root, "docs"), "Toc.html"); if (docs.exists()) { return docs.toURI().toURL().toString(); } else { return null; } } catch (Exception e) { return null; } } private void addTab(File f) { addTab(f.getName(), StartupDetails.open(f)); } private void addTab(String title, StartupDetails args) { Tab tab = new Tab(); setTab(tab, title, args, null); tabPane.getTabs().add(tab); tabPane.getSelectionModel().select(tab); } private void setTab(Tab tab, String title, StartupDetails args, Map options) { if (title==null && args.getFile()!=null) { title = args.getFile().getAbsolutePath(); } if (title!=null) { tab.setText(title); setGraphic(title, tab); } setupEditor(tab, args.getFile(), options); } private void setGraphic(String fileName, Tab t) { boolean source = !fileName.endsWith(".pef"); t.setGraphic(buildImage(this.getClass() .getResource(source ? "resource-files/source-doc.png" : "resource-files/braille-doc.png"))); } private void addSourceTab(File source, Map options) { Tab tab = new Tab(); setGraphic(source.getName(), tab); tab.setText(source.getName()); setupEditor(tab, source, options); tabPane.getTabs().add(tab); tabPane.getSelectionModel().select(tab); } private void setupEditor(Tab tab, File source, Map options) { AnnotatedFile ai = IdentityProvider.newInstance().identify(source); if (SourcePreviewController.supportsFormat(ai) && FeatureSwitch.EDITOR.isOn()) { if (options==null && FeatureSwitch.OPEN_OTHER_TYPES.isOn()) { EditorController prv = new EditorController(); tab.setOnClosed(ev -> { prv.closing(); }); prv.load(source, SourcePreviewController.isXML(ai)); tab.setContent(prv); } else { SourcePreviewController prv = new SourcePreviewController(); tab.setOnClosed(ev -> { prv.closing(); }); prv.open(ai, options); tab.setContent(prv); } } else { PreviewController prv = new PreviewController(); tab.setOnClosed(ev -> { prv.closing(); }); prv.open(ai, options); tab.setContent(prv); } tab.setOnCloseRequest(ev->{ if (((Editor)tab.getContent()).isModified()) { Alert alert = new Alert(AlertType.CONFIRMATION, Messages.MESSAGE_CONFIRM_CLOSE_UNSAVED_CHANGES.localize(source.getName()), ButtonType.YES, ButtonType.CANCEL); Optional res = alert.showAndWait(); if (res.map(v->(Boolean)!v.equals(ButtonType.YES)).orElse(true)) { ev.consume(); } } }); Editor prv = ((Editor)tab.getContent()); prv.modifiedProperty().addListener((o, ov, nv)->{ String modified = "*"; String t = tab.getText(); if (nv) { if (!t.startsWith(modified)) { tab.setText(modified + tab.getText()); } } else { if (t.startsWith(modified)) { tab.setText(t.substring(modified.length())); } } }); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy