/*
* Freeplane - mind map editor
* Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev
*
* This file is created by Dimitry Polivaev in 2008.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.features.note.mindmapmode;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.net.URI;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.html.StyleSheet;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.undo.IActor;
import org.freeplane.core.util.LogUtils;
import org.freeplane.features.map.IMapSelection;
import org.freeplane.features.map.MapController;
import org.freeplane.features.map.MapModel;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.mode.Controller;
import org.freeplane.features.mode.ModeController;
import org.freeplane.features.nodestyle.NodeStyleController;
import org.freeplane.features.note.NoteController;
import org.freeplane.features.note.NoteModel;
import org.freeplane.features.spellchecker.mindmapmode.SpellCheckerController;
import org.freeplane.features.styles.MapStyle;
import org.freeplane.features.styles.MapStyleModel;
import org.freeplane.features.styles.mindmapmode.SetBooleanMapPropertyAction;
import org.freeplane.features.text.mindmapmode.FreeplaneToSHTMLPropertyChangeAdapter;
import org.freeplane.features.text.mindmapmode.MTextController;
import org.freeplane.features.url.UrlManager;
import com.lightdev.app.shtm.SHTMLEditorPane;
import com.lightdev.app.shtm.SHTMLPanel;
/**
* @author Dimitry Polivaev
*/
public class MNoteController extends NoteController {
final class NoteDocumentListener implements DocumentListener {
public void changedUpdate(final DocumentEvent arg0) {
docEvent();
}
private void docEvent() {
final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner == null || !SwingUtilities.isDescendingFrom(focusOwner, htmlEditorPanel)) {
return;
}
final ModeController modeController = Controller.getCurrentModeController();
final MapController mapController = modeController.getMapController();
final MapModel map = modeController.getController().getMap();
mapController.setSaved(map, false);
}
public void insertUpdate(final DocumentEvent arg0) {
docEvent();
}
public void removeUpdate(final DocumentEvent arg0) {
docEvent();
}
}
private static class SouthPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
public SouthPanel() {
super(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10));
}
@Override
protected boolean processKeyBinding(final KeyStroke ks, final KeyEvent e, final int condition,
final boolean pressed) {
return super.processKeyBinding(ks, e, condition, pressed) || e.getKeyChar() == KeyEvent.VK_SPACE
|| e.getKeyChar() == KeyEvent.VK_ALT;
}
}
private static SHTMLPanel htmlEditorPanel;
public static final String RESOURCES_REMOVE_NOTES_WITHOUT_QUESTION = "remove_notes_without_question";
public static final String RESOURCES_USE_DEFAULT_FONT_FOR_NOTES_TOO = "resources_use_default_font_for_notes_too";
public static final String RESOURCES_USE_MARGIN_TOP_ZERO_FOR_NOTES = "resources_use_margin_top_zero_for_notes";
static final String RESOURCES_USE_SPLIT_PANE = "use_split_pane";
/**
* Indicates, whether or not the main panel has to be refreshed with new
* content. The typical content will be empty, so this state is saved here.
*/
private boolean mLastContentEmpty = true;
private NoteManager noteManager;
private SHTMLPanel noteViewerComponent;
/**
* @param modeController
*/
public MNoteController(ModeController modeController) {
super();
noteManager = new NoteManager(this);
createActions(modeController);
}
private void createActions(ModeController modeController) {
modeController.addAction(new SelectNoteAction(this));
modeController.addAction(new ShowHideNoteAction(this));
modeController.addAction(new EditNoteInDialogAction());
modeController.addAction(new SetNoteWindowPosition("top"));
modeController.addAction(new SetNoteWindowPosition( "left"));
modeController.addAction(new SetNoteWindowPosition("right"));
modeController.addAction(new SetNoteWindowPosition("bottom"));
modeController.addAction(new RemoveNoteAction(this));
modeController.addAction(new SetBooleanMapPropertyAction(SHOW_NOTE_ICONS));
}
SHTMLPanel getHtmlEditorPanel() {
if (htmlEditorPanel != null) {
return htmlEditorPanel;
}
htmlEditorPanel = MTextController.getController().createSHTMLPanel(NoteModel.EDITING_PURPOSE);
// make sure that SHTML gets notified of relevant config changes!
ResourceController.getResourceController().addPropertyChangeListener(
new FreeplaneToSHTMLPropertyChangeAdapter(htmlEditorPanel));
htmlEditorPanel.setMinimumSize(new Dimension(100, 100));
final SHTMLEditorPane editorPane = (SHTMLEditorPane) htmlEditorPanel.getEditorPane();
for (InputMap inputMap = editorPane.getInputMap(); inputMap != null; inputMap = inputMap.getParent()){
inputMap.remove(KeyStroke.getKeyStroke("ctrl pressed T"));
inputMap.remove(KeyStroke.getKeyStroke("ctrl shift pressed T"));
inputMap.remove(KeyStroke.getKeyStroke("ctrl pressed SPACE"));
}
editorPane.addFocusListener(new FocusListener() {
private SpellCheckerController spellCheckerController = null;
private boolean enabled = false;
public void focusLost(final FocusEvent e) {
if(! e.isTemporary()){
spellCheckerController.enableAutoSpell(editorPane, false);
enabled = false;
}
}
public void focusGained(final FocusEvent e) {
if(! enabled){
initSpellChecker();
spellCheckerController.enableAutoSpell(editorPane, true);
enabled = true;
}
}
private void initSpellChecker() {
if (spellCheckerController != null) {
return;
}
spellCheckerController = SpellCheckerController.getController();
spellCheckerController.addSpellCheckerMenu(editorPane.getPopup());
spellCheckerController.enableShortKey(editorPane, true);
}
});
return htmlEditorPanel;
}
SHTMLPanel getNoteViewerComponent() {
return noteViewerComponent;
}
void hideNotesPanel() {
noteManager.saveNote();
noteViewerComponent.setVisible(false);
Controller.getCurrentModeController().getController().getViewController().removeSplitPane();
ResourceController.getResourceController().setProperty(MNoteController.RESOURCES_USE_SPLIT_PANE, "false");
}
boolean isLastContentEmpty() {
return mLastContentEmpty;
}
@Override
protected void onWrite(final MapModel map) {
final ModeController modeController = Controller.getCurrentModeController();
final Controller controller = modeController.getController();
final IMapSelection selection = controller.getSelection();
if (selection == null) {
return;
}
final NodeModel selected = selection.getSelected();
noteManager.saveNote(selected);
}
void setLastContentEmpty(final boolean mLastContentEmpty) {
this.mLastContentEmpty = mLastContentEmpty;
}
public void setNoteText(final NodeModel node, final String newText) {
final String oldText = getNoteText(node);
if (oldText == newText || null != oldText && oldText.equals(newText)) {
return;
}
final IActor actor = new IActor() {
public void act() {
setText(newText);
}
public String getDescription() {
return "setNoteText";
}
private void setText(final String text) {
final boolean enabled = !(text == null || text.equals(""));
if (enabled) {
final NoteModel note = NoteModel.createNote(node);
note.setHtml(text);
node.addExtension(note);
}
else {
if (null != node.getExtension(NoteModel.class)) {
node.removeExtension(NoteModel.class);
}
}
Controller.getCurrentModeController().getMapController().nodeChanged(node, NodeModel.NOTE_TEXT, oldText, text);
if(noteManager != null)
noteManager.updateEditor();
}
public void undo() {
setText(oldText);
}
};
Controller.getCurrentModeController().execute(actor, node.getMap());
}
private boolean shouldUseSplitPane() {
return "true".equals(ResourceController.getResourceController().getProperty(
MNoteController.RESOURCES_USE_SPLIT_PANE));
}
void showNotesPanel(final boolean requestFocus) {
if (noteViewerComponent == null) {
noteViewerComponent = getHtmlEditorPanel();
noteManager.updateEditor();
}
final SouthPanel southPanel = new SouthPanel();
southPanel.add(noteViewerComponent, BorderLayout.CENTER);
// setDefaultFont();
noteViewerComponent.setOpenHyperlinkHandler(new ActionListener() {
public void actionPerformed(final ActionEvent pE) {
try {
String uriText = pE.getActionCommand();
UrlManager.getController().loadURL(new URI(uriText));
}
catch (final Exception e) {
LogUtils.severe(e);
}
}
});
noteViewerComponent.setVisible(true);
ResourceController.getResourceController().setProperty(MNoteController.RESOURCES_USE_SPLIT_PANE, "true");
Controller.getCurrentModeController().getController().getViewController().insertComponentIntoSplitPane(southPanel);
if (requestFocus) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
EventQueue.invokeLater(new Runnable() {
public void run() {
final SHTMLPanel htmlEditorPanel = getHtmlEditorPanel();
htmlEditorPanel.getMostRecentFocusOwner().requestFocus();
if (ResourceController.getResourceController().getBooleanProperty("goto_note_end_on_edit")) {
final JEditorPane editorPane = htmlEditorPanel.getEditorPane();
editorPane.setCaretPosition(editorPane.getDocument().getLength());
}
}
});
}
southPanel.revalidate();
}
void setDefaultFont() {
final StyleSheet styleSheet = noteViewerComponent.getDocument().getStyleSheet();
styleSheet.removeStyle("body");
styleSheet.removeStyle("p");
// set default font for notes:
final NodeStyleController style = (NodeStyleController) Controller.getCurrentModeController().getExtension(
NodeStyleController.class);
MapModel map = Controller.getCurrentModeController().getController().getMap();
if(map != null){
final Font defaultFont;
defaultFont = style.getDefaultFont(map, MapStyleModel.NOTE_STYLE);
String rule = "body {";
rule += "font-family: " + defaultFont.getFamily() + ";";
rule += "font-size: " + defaultFont.getSize() + "pt;";
if (defaultFont.isItalic()) {
rule += "font-style: italic; ";
}
if (defaultFont.isBold()) {
rule += "font-weight: bold; ";
}
rule += "}\n";
styleSheet.addRule(rule);
}
if (ResourceController.getResourceController().getBooleanProperty(
MNoteController.RESOURCES_USE_MARGIN_TOP_ZERO_FOR_NOTES)) {
/* this is used for paragraph spacing. I put it here, too, as
* the tooltip display uses the same spacing. But it is to be discussed.
* fc, 23.3.2009.
*/
String rule = "p {";
rule += "margin-top:0;";
rule += "}\n";
styleSheet.addRule(rule);
}
}
boolean isEditing() {
final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
return focusOwner != null && noteViewerComponent != null && SwingUtilities.isDescendingFrom(focusOwner, noteViewerComponent);
}
void setFocusToMap() {
final Controller controller = Controller.getCurrentModeController().getController();
final NodeModel node = controller.getSelection().getSelected();
controller.getViewController().getComponent(node).requestFocusInWindow();
}
public void shutdownController() {
Controller.getCurrentModeController().getMapController().removeNodeSelectionListener(noteManager);
if (noteViewerComponent == null) {
return;
}
noteViewerComponent.getActionMap().remove("jumpToMapAction");
if (shouldUseSplitPane()) {
hideNotesPanel();
noteViewerComponent = null;
}
}
public void startupController() {
final ModeController modeController = Controller.getCurrentModeController();
if (shouldUseSplitPane()) {
SwingUtilities.invokeLater(new Runnable() {
int count = 10;
public void run() {
if(count == 0){
showNotesPanel(false);
final IMapSelection selection = Controller.getCurrentController().getSelection();
if(selection != null){
final NodeModel selected = selection.getSelected();
if(selected != null){
selection.centerNode(selected);
}
}
}
else{
count--;
SwingUtilities.invokeLater(this);
}
}
});
}
modeController.getMapController().addNodeSelectionListener(noteManager);
noteManager.mNoteDocumentListener = new NoteDocumentListener();
}
boolean isNoteEditorShowing() {
return ResourceController.getResourceController().getBooleanProperty(
MNoteController.RESOURCES_USE_SPLIT_PANE);
}
public void setShowNotesInMap(final MapModel model, final boolean show) {
MapStyle.getController().setProperty(model, SHOW_NOTES_IN_MAP, Boolean.toString(show));
}
}