/*
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.markdownwriterfx;

import java.text.MessageFormat;
import java.util.function.Function;
import java.util.prefs.Preferences;
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.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;
import javafx.event.Event;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.ChoiceBox;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.Separator;
import javafx.scene.control.SplitPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import org.controlsfx.control.PopOver;
import org.controlsfx.control.PopOver.ArrowLocation;
import org.markdownwriterfx.editor.MarkdownEditorPane;
import org.markdownwriterfx.editor.SmartEdit;
import org.markdownwriterfx.options.MarkdownExtensionsPane;
import org.markdownwriterfx.options.Options;
import org.markdownwriterfx.options.Options.RendererType;
import org.markdownwriterfx.options.OptionsDialog;
import org.markdownwriterfx.preview.MarkdownPreviewPane;
import org.markdownwriterfx.projects.ProjectManager;
import org.markdownwriterfx.projects.ProjectPane;
import org.markdownwriterfx.util.Action;
import org.markdownwriterfx.util.ActionUtils;
import org.markdownwriterfx.util.Utils;
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;

/**
 * Main window containing a tab pane in the center for file editors.
 *
 * @author Karl Tauber
 */
class MainWindow
{
	private final Scene scene;
	private final ProjectPane projectPane;
	private final FileEditorTabPane fileEditorTabPane;
	private final FileEditorManager fileEditorManager;
	private MenuBar menuBar;
	private Node extensionsButton;
	final BooleanProperty stageFocusedProperty = new SimpleBooleanProperty();

	MainWindow() {
		fileEditorTabPane = new FileEditorTabPane(this);
		fileEditorManager = new FileEditorManager(fileEditorTabPane);
		projectPane = new ProjectPane(fileEditorManager);

		Preferences state = MarkdownWriterFXApp.getState();
		double dividerPosition = state.getDouble("projectPaneDividerPosition", 0.2);

		SplitPane splitPane = new SplitPane(projectPane.getNode(), fileEditorTabPane.getNode()) {
			private int layoutCount;

			@Override
			protected void layoutChildren() {
				super.layoutChildren();
				if (layoutCount < 2) {
					layoutCount++;
					setDividerPosition(0, dividerPosition);
					super.layoutChildren();
				}
			}
		};
		SplitPane.setResizableWithParent(projectPane.getNode(), false);
		splitPane.setDividerPosition(0, dividerPosition);
		splitPane.getDividers().get(0).positionProperty().addListener((ob, o, n) -> {
			Utils.putPrefsDouble(state, "projectPaneDividerPosition", n.doubleValue(), 0.2);
		});

		BorderPane borderPane = new BorderPane();
		borderPane.getStyleClass().add("main");
		borderPane.setPrefSize(800, 800);
		borderPane.setTop(createMenuBarAndToolBar());
		borderPane.setCenter(splitPane);

		scene = new Scene(borderPane);
		scene.getStylesheets().add("org/markdownwriterfx/MarkdownWriter.css");
		scene.windowProperty().addListener((observable, oldWindow, newWindow) -> {
			newWindow.setOnCloseRequest(e -> {
				if (!fileEditorTabPane.canCloseAllEditos())
					e.consume();
			});

			// workaround for a bug in JavaFX: unselect menubar if window looses focus
			newWindow.focusedProperty().addListener((obs, oldFocused, newFocused) -> {
				if (!newFocused) {
					// send an ESC key event to the menubar
					menuBar.fireEvent(new KeyEvent(KeyEvent.KEY_PRESSED,
							KeyEvent.CHAR_UNDEFINED, "", KeyCode.ESCAPE,
							false, false, false, false));
				}
			});
		});

		Utils.fixSpaceAfterDeadKey(scene);

		// workaround for a bad JavaFX behavior: menu bar always grabs focus when ALT key is pressed,
		// but should grab it when ALT key is releases (as all other UI toolkits do) to give other
		// controls the chance to use Alt+Key shortcuts.
		scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
			if (e.isAltDown())
				e.consume();
		});

		// open markdown files dropped to main window
		scene.setOnDragOver(e -> {
			if (e.getDragboard().hasFiles())
				e.acceptTransferModes(TransferMode.COPY);
			e.consume();
		});
		scene.setOnDragDropped(e -> {
			boolean success = false;
			if (e.getDragboard().hasFiles()) {
				fileEditorTabPane.openEditors(e.getDragboard().getFiles(), 0, -1);
				success = true;
			}
			e.setDropCompleted(success);
			e.consume();
		});

		Platform.runLater(() -> stageFocusedProperty.bind(scene.getWindow().focusedProperty()));
	}

	Scene getScene() {
		return scene;
	}

	private Node createMenuBarAndToolBar() {
		BooleanBinding activeFileEditorIsNull = fileEditorTabPane.activeFileEditorProperty().isNull();

		// File actions
		Action fileNewAction = new Action(Messages.get("MainWindow.fileNewAction"), "Shortcut+N", FILE_ALT, e -> fileNew());
		Action fileOpenAction = new Action(Messages.get("MainWindow.fileOpenAction"), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen());
		Action fileOpenProjectAction = new Action(Messages.get("MainWindow.fileOpenProjectAction"), "Shortcut+Shift+O", FOLDER_OPEN, e -> fileOpenProject());
		Action fileCloseAction = new Action(Messages.get("MainWindow.fileCloseAction"), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull);
		Action fileCloseAllAction = new Action(Messages.get("MainWindow.fileCloseAllAction"), null, null, e -> fileCloseAll(), activeFileEditorIsNull);
		Action fileSaveAction = new Action(Messages.get("MainWindow.fileSaveAction"), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
				createActiveBooleanProperty(FileEditor::modifiedProperty).not());
		Action fileSaveAsAction = new Action(Messages.get("MainWindow.fileSaveAsAction"), null, null, e -> fileSaveAs(),
				activeFileEditorIsNull.or(createActiveBooleanProperty(FileEditor::readOnlyProperty)));
		Action fileSaveAllAction = new Action(Messages.get("MainWindow.fileSaveAllAction"), "Shortcut+Shift+S", null, e -> fileSaveAll(),
				Bindings.not(fileEditorTabPane.anyFileEditorModifiedProperty()));
		Action fileExitAction = new Action(Messages.get("MainWindow.fileExitAction"), null, null, e -> fileExit());

		// Edit actions
		Action editUndoAction = new Action(Messages.get("MainWindow.editUndoAction"), "Shortcut+Z", UNDO,
				e -> getActiveEditor().undo(),
				createActiveBooleanProperty(FileEditor::canUndoProperty).not());
		Action editRedoAction = new Action(Messages.get("MainWindow.editRedoAction"), "Shortcut+Y", REPEAT,
				e -> getActiveEditor().redo(),
				createActiveBooleanProperty(FileEditor::canRedoProperty).not());
		Action editCutAction = new Action(Messages.get("MainWindow.editCutAction"), "Shortcut+X", CUT,
				e -> getActiveEditor().cut(),
				activeFileEditorIsNull);
		Action editCopyAction = new Action(Messages.get("MainWindow.editCopyAction"), "Shortcut+C", COPY,
				e -> getActiveEditor().copy(),
				activeFileEditorIsNull);
		Action editPasteAction = new Action(Messages.get("MainWindow.editPasteAction"), "Shortcut+V", PASTE,
				e -> getActiveEditor().paste(),
				activeFileEditorIsNull);
		Action editSelectAllAction = new Action(Messages.get("MainWindow.editSelectAllAction"), "Shortcut+A", null,
				e -> getActiveEditor().selectAll(),
				activeFileEditorIsNull);
		Action editFindAction = new Action(Messages.get("MainWindow.editFindAction"), "Shortcut+F", SEARCH,
				e -> getActiveEditor().find(false),
				activeFileEditorIsNull);
		Action editReplaceAction = new Action(Messages.get("MainWindow.editReplaceAction"), "Shortcut+H", RETWEET,
				e -> getActiveEditor().find(true),
				activeFileEditorIsNull);
		Action editFindNextAction = new Action(Messages.get("MainWindow.editFindNextAction"), "F3", null,
				e -> getActiveEditor().findNextPrevious(true),
				activeFileEditorIsNull);
		Action editFindPreviousAction = new Action(Messages.get("MainWindow.editFindPreviousAction"), "Shift+F3", null,
				e -> getActiveEditor().findNextPrevious(false),
				activeFileEditorIsNull);

		Action editFormatAllAction = new Action(Messages.get("MainWindow.editFormatAll"), "Shortcut+Shift+F", null,
				e -> getActiveSmartEdit().format(false, null),
				activeFileEditorIsNull);
		Action editFormatSelectionAction = new Action(Messages.get("MainWindow.editFormatSelection"), "Shortcut+Shift+Alt+F", null,
				e -> getActiveSmartEdit().format(true, null),
				activeFileEditorIsNull);

		// View actions
		Action viewPreviewAction = new Action(Messages.get("MainWindow.viewPreviewAction"), null, EYE,
				null, null, fileEditorTabPane.previewVisible);
		Action viewHtmlSourceAction = new Action(Messages.get("MainWindow.viewHtmlSourceAction"), null, HTML5,
				null, null, fileEditorTabPane.htmlSourceVisible);
		Action viewMarkdownAstAction = new Action(Messages.get("MainWindow.viewMarkdownAstAction"), null, SITEMAP,
				null, null, fileEditorTabPane.markdownAstVisible);
		Action viewExternalAction = MarkdownPreviewPane.hasExternalPreview()
			? new Action(Messages.get("MainWindow.viewExternalAction"), null, EXTERNAL_LINK,
		        null, null, fileEditorTabPane.externalVisible)
			: null;
		Action viewShowLineNoAction = new Action(Messages.get("MainWindow.viewShowLineNoAction"), null, null,
				null, null, Options.showLineNoProperty());
		Action viewShowWhitespaceAction = new Action(Messages.get("MainWindow.viewShowWhitespaceAction"), "Alt+W", null,
				null, null, Options.showWhitespaceProperty());
		Action viewShowImagesEmbeddedAction = new Action(Messages.get("MainWindow.viewShowImagesEmbeddedAction"), "Alt+I", null,
				null, null, Options.showImagesEmbeddedProperty());

		// Insert actions
		Action insertBoldAction = new Action(Messages.get("MainWindow.insertBoldAction"), "Shortcut+B", BOLD,
				e -> getActiveSmartEdit().insertBold(Messages.get("MainWindow.insertBoldText")),
				activeFileEditorIsNull);
		Action insertItalicAction = new Action(Messages.get("MainWindow.insertItalicAction"), "Shortcut+I", ITALIC,
				e -> getActiveSmartEdit().insertItalic(Messages.get("MainWindow.insertItalicText")),
				activeFileEditorIsNull);
		Action insertStrikethroughAction = new Action(Messages.get("MainWindow.insertStrikethroughAction"), "Shortcut+T", STRIKETHROUGH,
				e -> getActiveSmartEdit().insertStrikethrough(Messages.get("MainWindow.insertStrikethroughText")),
				activeFileEditorIsNull);
		Action insertCodeAction = new Action(Messages.get("MainWindow.insertCodeAction"), "Shortcut+K", CODE,
				e -> getActiveSmartEdit().insertInlineCode(Messages.get("MainWindow.insertCodeText")),
				activeFileEditorIsNull);

		Action insertLinkAction = new Action(Messages.get("MainWindow.insertLinkAction"), "Shortcut+L", LINK,
				e -> getActiveSmartEdit().insertLink(),
				activeFileEditorIsNull);
		Action insertImageAction = new Action(Messages.get("MainWindow.insertImageAction"), "Shortcut+G", PICTURE_ALT,
				e -> getActiveSmartEdit().insertImage(),
				activeFileEditorIsNull);

		Action insertUnorderedListAction = new Action(Messages.get("MainWindow.insertUnorderedListAction"), "Shortcut+U", LIST_UL,
				e -> getActiveSmartEdit().insertUnorderedList(),
				activeFileEditorIsNull);
		Action insertOrderedListAction = new Action(Messages.get("MainWindow.insertOrderedListAction"), "Shortcut+Shift+U", LIST_OL,
				e -> getActiveSmartEdit().surroundSelection("\n\n1. ", ""),
				activeFileEditorIsNull);
		Action insertBlockquoteAction = new Action(Messages.get("MainWindow.insertBlockquoteAction"), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
				e -> getActiveSmartEdit().surroundSelection("\n\n> ", ""),
				activeFileEditorIsNull);
		Action insertFencedCodeBlockAction = new Action(Messages.get("MainWindow.insertFencedCodeBlockAction"), "Shortcut+Shift+K", FILE_CODE_ALT,
				e -> getActiveSmartEdit().surroundSelection("\n\n```\n", "\n```\n\n", Messages.get("MainWindow.insertFencedCodeBlockText")),
				activeFileEditorIsNull);

		Action insertHeader1Action = new Action(Messages.get("MainWindow.insertHeader1Action"), "Shortcut+1", HEADER,
				e -> getActiveSmartEdit().insertHeading(1, Messages.get("MainWindow.insertHeader1Text")),
				activeFileEditorIsNull);
		Action insertHeader2Action = new Action(Messages.get("MainWindow.insertHeader2Action"), "Shortcut+2", HEADER,
				e -> getActiveSmartEdit().insertHeading(2, Messages.get("MainWindow.insertHeader2Text")),
				activeFileEditorIsNull);
		Action insertHeader3Action = new Action(Messages.get("MainWindow.insertHeader3Action"), "Shortcut+3", HEADER,
				e -> getActiveSmartEdit().insertHeading(3, Messages.get("MainWindow.insertHeader3Text")),
				activeFileEditorIsNull);
		Action insertHeader4Action = new Action(Messages.get("MainWindow.insertHeader4Action"), "Shortcut+4", HEADER,
				e -> getActiveSmartEdit().insertHeading(4, Messages.get("MainWindow.insertHeader4Text")),
				activeFileEditorIsNull);
		Action insertHeader5Action = new Action(Messages.get("MainWindow.insertHeader5Action"), "Shortcut+5", HEADER,
				e -> getActiveSmartEdit().insertHeading(5, Messages.get("MainWindow.insertHeader5Text")),
				activeFileEditorIsNull);
		Action insertHeader6Action = new Action(Messages.get("MainWindow.insertHeader6Action"), "Shortcut+6", HEADER,
				e -> getActiveSmartEdit().insertHeading(6, Messages.get("MainWindow.insertHeader6Text")),
				activeFileEditorIsNull);

		Action insertHorizontalRuleAction = new Action(Messages.get("MainWindow.insertHorizontalRuleAction"), null, null,
				e -> getActiveSmartEdit().surroundSelection("\n\n---\n\n", ""),
				activeFileEditorIsNull);

		// Tools actions
		Action toolsOptionsAction = new Action(Messages.get("MainWindow.toolsOptionsAction"), "Shortcut+,", null, e -> toolsOptions());

		// Help actions
		Action helpAboutAction = new Action(Messages.get("MainWindow.helpAboutAction"), null, null, e -> helpAbout());


		//---- MenuBar ----

		Menu fileMenu = ActionUtils.createMenu(Messages.get("MainWindow.fileMenu"),
				fileNewAction,
				fileOpenAction,
				fileOpenProjectAction,
				null,
				fileCloseAction,
				fileCloseAllAction,
				null,
				fileSaveAction,
				fileSaveAsAction,
				fileSaveAllAction,
				null,
				fileExitAction);

		Menu editMenu = ActionUtils.createMenu(Messages.get("MainWindow.editMenu"),
				editUndoAction,
				editRedoAction,
				null,
				editCutAction,
				editCopyAction,
				editPasteAction,
				editSelectAllAction,
				null,
				editFindAction,
				editReplaceAction,
				null,
				editFindNextAction,
				editFindPreviousAction,
				null,
				editFormatAllAction,
				editFormatSelectionAction);

		Menu viewMenu = ActionUtils.createMenu(Messages.get("MainWindow.viewMenu"),
				viewPreviewAction,
				viewHtmlSourceAction,
				viewMarkdownAstAction,
				null,
				viewShowLineNoAction,
				viewShowWhitespaceAction,
				viewShowImagesEmbeddedAction);
		if (viewExternalAction != null)
			viewMenu.getItems().add(3, ActionUtils.createMenuItem(viewExternalAction));

		Menu insertMenu = ActionUtils.createMenu(Messages.get("MainWindow.insertMenu"),
				insertBoldAction,
				insertItalicAction,
				insertStrikethroughAction,
				insertCodeAction,
				null,
				insertLinkAction,
				insertImageAction,
				null,
				insertUnorderedListAction,
				insertOrderedListAction,
				insertBlockquoteAction,
				insertFencedCodeBlockAction,
				null,
				insertHeader1Action,
				insertHeader2Action,
				insertHeader3Action,
				insertHeader4Action,
				insertHeader5Action,
				insertHeader6Action,
				null,
				insertHorizontalRuleAction);

		Menu toolsMenu = ActionUtils.createMenu(Messages.get("MainWindow.toolsMenu"),
				toolsOptionsAction);

		Menu helpMenu = ActionUtils.createMenu(Messages.get("MainWindow.helpMenu"),
				helpAboutAction);

		menuBar = new MenuBar(fileMenu, editMenu, viewMenu, insertMenu, toolsMenu, helpMenu);


		//---- ToolBar ----

		ToolBar toolBar = ActionUtils.createToolBar(
				fileNewAction,
				fileOpenAction,
				fileOpenProjectAction,
				fileSaveAction,
				null,
				editUndoAction,
				editRedoAction,
				null,
				new Action(insertBoldAction, createActiveEditBooleanProperty(SmartEdit::boldProperty)),
				new Action(insertItalicAction, createActiveEditBooleanProperty(SmartEdit::italicProperty)),
				new Action(insertCodeAction, createActiveEditBooleanProperty(SmartEdit::codeProperty)),
				null,
				new Action(insertLinkAction, createActiveEditBooleanProperty(SmartEdit::linkProperty)),
				new Action(insertImageAction, createActiveEditBooleanProperty(SmartEdit::imageProperty)),
				null,
				new Action(insertUnorderedListAction, createActiveEditBooleanProperty(SmartEdit::unorderedListProperty)),
				new Action(insertOrderedListAction, createActiveEditBooleanProperty(SmartEdit::orderedListProperty)),
				new Action(insertBlockquoteAction, createActiveEditBooleanProperty(SmartEdit::blockquoteProperty)),
				new Action(insertFencedCodeBlockAction, createActiveEditBooleanProperty(SmartEdit::fencedCodeProperty)),
				null,
				new Action(insertHeader1Action, createActiveEditBooleanProperty(SmartEdit::headerProperty)));

		// horizontal spacer
		Region spacer = new Region();
		HBox.setHgrow(spacer, Priority.ALWAYS);
		toolBar.getItems().add(spacer);

		// preview renderer type choice box
		ChoiceBox<RendererType> previewRenderer = new ChoiceBox<>();
		previewRenderer.setFocusTraversable(false);
		previewRenderer.getItems().addAll(RendererType.values());
		previewRenderer.getSelectionModel().select(Options.getMarkdownRenderer());
		previewRenderer.getSelectionModel().selectedItemProperty().addListener((ob, o, n) -> {
			Options.setMarkdownRenderer(n);
		});
		Options.markdownRendererProperty().addListener((ob, o, n) -> {
			previewRenderer.getSelectionModel().select(n);
		});
		toolBar.getItems().add(previewRenderer);

		// markdown extensions popover
		String title = Messages.get("MainWindow.MarkdownExtensions");
		extensionsButton = ActionUtils.createToolBarButton(
				new Action(title, null, COG, e -> {
					PopOver popOver = new PopOver();
					popOver.setTitle(title);
					popOver.setHeaderAlwaysVisible(true);
					popOver.setArrowLocation(ArrowLocation.TOP_CENTER);
					popOver.setContentNode(new MarkdownExtensionsPane(true));
					popOver.show(extensionsButton);
				}));
		toolBar.getItems().add(extensionsButton);
		toolBar.getItems().add(new Separator());

		// preview actions
		Node[] previewButtons = ActionUtils.createToolBarButtons(
				viewPreviewAction,
				viewHtmlSourceAction,
				viewMarkdownAstAction);
		ToggleGroup viewGroup = new ToggleGroup();
		for (Node n : previewButtons)
			((ToggleButton)n).setToggleGroup(viewGroup);
		toolBar.getItems().addAll(previewButtons);

		if (viewExternalAction != null) {
			ButtonBase externalPreviewButton = ActionUtils.createToolBarButton(viewExternalAction);
			((ToggleButton)externalPreviewButton).setToggleGroup(viewGroup);
			toolBar.getItems().add(externalPreviewButton);
		}

		return new VBox(menuBar, toolBar);
	}

	private MarkdownEditorPane getActiveEditor() {
		return fileEditorTabPane.getActiveFileEditor().getEditor();
	}

	private SmartEdit getActiveSmartEdit() {
		return getActiveEditor().getSmartEdit();
	}

	/**
	 * Creates a boolean property that is bound to another boolean value
	 * of the active editor.
	 */
	private BooleanProperty createActiveBooleanProperty(Function<FileEditor, ObservableBooleanValue> func) {
		BooleanProperty b = new SimpleBooleanProperty();
		FileEditor fileEditor = fileEditorTabPane.getActiveFileEditor();
		if (fileEditor != null)
			b.bind(func.apply(fileEditor));
		fileEditorTabPane.activeFileEditorProperty().addListener((observable, oldFileEditor, newFileEditor) -> {
			b.unbind();
			if (newFileEditor != null)
				b.bind(func.apply(newFileEditor));
			else
				b.set(false);
		});
		return b;
	}

	/**
	 * Creates a boolean property that is bound to another boolean value
	 * of the active editor's SmartEdit.
	 */
	private BooleanProperty createActiveEditBooleanProperty(Function<SmartEdit, ObservableBooleanValue> func) {
		BooleanProperty b = new SimpleBooleanProperty() {
			@Override
			public void set(boolean newValue) {
				// invoked when the user invokes an action
				// do not try to change SmartEdit properties because this
				// would throw a "bound value cannot be set" exception
			}
		};

		ChangeListener<? super FileEditor> listener = (observable, oldFileEditor, newFileEditor) -> {
			b.unbind();
			if (newFileEditor != null) {
				if (newFileEditor.getEditor() != null)
					b.bind(func.apply(newFileEditor.getEditor().getSmartEdit()));
				else {
					newFileEditor.editorProperty().addListener((ob, o, n) -> {
						b.bind(func.apply(n.getSmartEdit()));
					});
				}
			} else
				b.set(false);
		};
		FileEditor fileEditor = fileEditorTabPane.getActiveFileEditor();
		listener.changed(null, null, fileEditor);
		fileEditorTabPane.activeFileEditorProperty().addListener(listener);
		return b;
	}

	Alert createAlert(AlertType alertType, String title,
		String contentTextFormat, Object... contentTextArgs)
	{
		Alert alert = new Alert(alertType);
		alert.setTitle(title);
		alert.setHeaderText(null);
		alert.setContentText(MessageFormat.format(contentTextFormat, contentTextArgs));
		alert.initOwner(getScene().getWindow());
		return alert;
	}

	//---- File actions -------------------------------------------------------

	private void fileNew() {
		fileEditorTabPane.newEditor();
	}

	private void fileOpen() {
		fileEditorTabPane.openEditor();
	}

	private void fileOpenProject() {
		if (fileEditorManager.canOpenAnotherProject())
			ProjectManager.openProject(scene.getWindow());
	}

	private void fileClose() {
		fileEditorTabPane.closeEditor(fileEditorTabPane.getActiveFileEditor(), true);
	}

	private void fileCloseAll() {
		fileEditorTabPane.closeAllEditors(true);
	}

	private void fileSave() {
		fileEditorTabPane.saveEditor(fileEditorTabPane.getActiveFileEditor());
	}

	private void fileSaveAs() {
		fileEditorTabPane.saveEditorAs(fileEditorTabPane.getActiveFileEditor());
	}

	private void fileSaveAll() {
		fileEditorTabPane.saveAllEditors();
	}

	private void fileExit() {
		Window window = scene.getWindow();
		Event.fireEvent(window, new WindowEvent(window, WindowEvent.WINDOW_CLOSE_REQUEST));
	}

	//---- Tools actions ------------------------------------------------------

	private void toolsOptions() {
		OptionsDialog dialog = new OptionsDialog(getScene().getWindow());
		dialog.showAndWait();
	}

	//---- Help actions -------------------------------------------------------

	private void helpAbout() {
		String version = null;
		Package pkg = this.getClass().getPackage();
		if (pkg != null)
			version = pkg.getImplementationVersion();
		if (version == null)
			version = "(dev)";

		Alert alert = new Alert(AlertType.INFORMATION);
		alert.setTitle(Messages.get("MainWindow.about.title"));
		alert.setHeaderText(Messages.get("MainWindow.about.headerText"));
		alert.setContentText(Messages.get("MainWindow.about.contentText", version));
		alert.setGraphic(new ImageView(new Image("org/markdownwriterfx/markdownwriterfx32.png")));
		alert.initOwner(getScene().getWindow());
		alert.getDialogPane().setPrefWidth(420);

		alert.showAndWait();
	}
}