/*
* Freeplane - mind map editor
* Copyright (C) 2008 Dimitry Polivaev
*
* This file author is Dimitry Polivaev
*
* 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.text;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.freeplane.core.extension.IExtension;
import org.freeplane.core.io.ReadManager;
import org.freeplane.core.io.WriteManager;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.util.ColorUtils;
import org.freeplane.core.util.HtmlUtils;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.filter.FilterController;
import org.freeplane.features.format.PatternFormat;
import org.freeplane.features.map.ITooltipProvider;
import org.freeplane.features.map.MapController;
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.nodestyle.NodeStyleModel;
import org.freeplane.features.styles.IStyle;
import org.freeplane.features.styles.LogicalStyleController;
import org.freeplane.features.styles.MapStyleModel;
/**
* @author Dimitry Polivaev
*/
public class TextController implements IExtension {
public static final String FILTER_NODE = "filter_node";
public static final String FILTER_ANYTEXT = "filter_any_text";
public static final String FILTER_NOTE = "filter_note";
public static final String FILTER_PARENT = "filter_parent";
public static final String FILTER_DETAILS = "filter_details";
private static final Integer NODE_TOOLTIP = 1;
private static final Integer DETAILS_TOOLTIP = 2;
private final List<IContentTransformer> textTransformers;
protected final ModeController modeController;
public static final String MARK_TRANSFORMED_TEXT = "highlight_formulas";
public static boolean isMarkTransformedTextSet() {
return Controller.getCurrentController().getResourceController().getBooleanProperty(MARK_TRANSFORMED_TEXT);
}
public static TextController getController() {
final ModeController modeController = Controller.getCurrentModeController();
return getController(modeController);
}
public static TextController getController(ModeController modeController) {
return (TextController) modeController.getExtension(TextController.class);
}
public static void install() {
FilterController.getCurrentFilterController().getConditionFactory().addConditionController(0,
new NodeTextConditionController());
}
public static void install( final TextController textController) {
final ModeController modeController = Controller.getCurrentModeController();
modeController.addExtension(TextController.class, textController);
}
public TextController(final ModeController modeController) {
super();
textTransformers = new LinkedList<IContentTransformer>();
this.modeController = modeController;
final MapController mapController = modeController.getMapController();
final ReadManager readManager = mapController.getReadManager();
final WriteManager writeManager = mapController.getWriteManager();
final NodeTextBuilder textBuilder = new NodeTextBuilder();
textBuilder.registerBy(readManager, writeManager);
writeManager.addExtensionElementWriter(DetailTextModel.class, textBuilder);
writeManager.addExtensionAttributeWriter(ShortenedTextModel.class, textBuilder);
modeController.addAction(new ToggleDetailsAction());
modeController.addAction(new SetShortenerStateAction());
// modeController.addAction(new ToggleNodeNumberingAction());
addTextTransformer(new FormatContentTransformer(this, 50));
registerDetailsTooltip();
registerNodeTextTooltip();
}
public void addTextTransformer(IContentTransformer textTransformer) {
textTransformers.add(textTransformer);
Collections.sort(textTransformers);
}
public List<IContentTransformer> getTextTransformers() {
return textTransformers;
}
public void removeTextTransformer(IContentTransformer textTransformer) {
textTransformers.remove(textTransformer);
}
public String getText(NodeModel nodeModel) {
return nodeModel.getText();
}
public Object getTransformedObject(Object object, final NodeModel nodeModel, Object extension) throws TransformationException{
if(object instanceof String && ResourceController.getResourceController().getBooleanProperty("parse_data")){
String string = (String) object;
if(string.length() > 0 && string.charAt(0) == '\''){
if(isTextFormattingDisabled(nodeModel))
return string;
else
return string.substring(1);
}
}
boolean markTransformation = false;
for (IContentTransformer textTransformer : getTextTransformers()) {
try {
Object in = object;
object = textTransformer.transformContent(this, in, nodeModel, extension);
markTransformation = markTransformation || textTransformer.markTransformation() && ! in.equals(object);
}
catch (RuntimeException e) {
throw new TransformationException(e);
}
}
if(markTransformation)
return new HighlightedTransformedObject(object);
else
return object;
}
public boolean isTextFormattingDisabled(final NodeModel nodeModel) {
return PatternFormat.IDENTITY_PATTERN.equals(getNodeFormat(nodeModel));
}
/** returns an error message instead of a normal result if something goes wrong. */
public Object getTransformedObjectNoThrow(Object data, final NodeModel node, Object extension) {
try {
return getTransformedObject(data, node, extension);
}
catch (Throwable e) {
LogUtils.warn(e.getMessage(), e);
return TextUtils.format("MainView.errorUpdateText", data, e.getLocalizedMessage());
}
}
public Object getTransformedObject(NodeModel node) throws TransformationException{
final Object userObject = node.getUserObject();
return getTransformedObject(userObject, node, userObject);
}
public Object getTransformedObjectNoThrow(NodeModel node) {
final Object userObject = node.getUserObject();
return getTransformedObjectNoThrow(userObject, node, userObject);
}
/** convenience method for getTransformedText().toString. */
public String getTransformedText(Object text, final NodeModel nodeModel, Object extension) throws TransformationException{
text = getTransformedObject(text, nodeModel, extension);
return text.toString();
}
public String getTransformedTextNoThrow(Object text, final NodeModel nodeModel, Object extension) {
text = getTransformedObjectNoThrow(text, nodeModel, extension);
return text.toString();
}
public boolean isMinimized(NodeModel node){
final ShortenedTextModel shortened = ShortenedTextModel.getShortenedTextModel(node);
return shortened != null;
}
// FIXME: This should be getPlainTransformedText() since getText() does not transform too
/** returns transformed text converted to plain text. */
public String getPlainTextContent(NodeModel nodeModel) {
final String text = getTransformedTextNoThrow(nodeModel);
return HtmlUtils.htmlToPlain(text);
}
public String getTransformedTextNoThrow(NodeModel nodeModel) {
final Object userObject = nodeModel.getUserObject();
final Object input;
if(userObject instanceof String && HtmlUtils.isHtmlNode((String) userObject))
input = HtmlUtils.htmlToPlain((String) userObject);
else
input = userObject;
final String text = getTransformedTextNoThrow(input, nodeModel, userObject);
return text;
}
public String getShortText(NodeModel nodeModel) {
String adaptedText = getPlainTextContent(nodeModel);
if (adaptedText.length() > 40) {
adaptedText = adaptedText.substring(0, 40) + " ...";
}
return adaptedText;
}
public void setDetailsHidden(NodeModel node, boolean isHidden) {
final DetailTextModel details = DetailTextModel.createDetailText(node);
if(isHidden == details.isHidden()){
return;
}
details.setHidden(isHidden);
node.addExtension(details);
Controller.getCurrentModeController().getMapController().nodeChanged(node, "DETAILS_HIDDEN", ! isHidden, isHidden);
}
private void registerDetailsTooltip() {
modeController.addToolTipProvider(DETAILS_TOOLTIP, new ITooltipProvider() {
public String getTooltip(ModeController modeController, NodeModel node, Component view) {
final DetailTextModel detailText = DetailTextModel.getDetailText(node);
if (detailText == null || ! (detailText.isHidden() || ShortenedTextModel.isShortened(node)) ){
return null;
}
final NodeStyleController style = (NodeStyleController) modeController.getExtension(NodeStyleController.class);
final MapStyleModel model = MapStyleModel.getExtension(node.getMap());
final NodeModel detailStyleNode = model.getStyleNodeSafe(MapStyleModel.DETAILS_STYLE);
Font detailFont = style.getFont(detailStyleNode);
Color detailBackground = style.getBackgroundColor(detailStyleNode);
Color detailForeground = style.getColor(detailStyleNode);
final StringBuilder rule = new StringBuilder();
rule.append("font-family: " + detailFont.getFamily() + ";");
rule.append("font-size: " + detailFont.getSize() + "pt;");
if (detailFont.isItalic()) {
rule.append("font-style: italic; ");
}
if (detailFont.isBold()) {
rule.append("font-weight: bold; ");
}
rule.append("color: ").append(ColorUtils.colorToString(detailForeground)).append(";");
rule.append("background-color: ").append(ColorUtils.colorToString(detailBackground)).append(";");
String noteText= detailText.getHtml();
final String tooltipText = noteText.replaceFirst("<body>", "<body><div style=\"" + rule + "\">")
.replaceFirst("</body>", "</div></body>");
return tooltipText;
}
});
}
private void registerNodeTextTooltip() {
modeController.addToolTipProvider(NODE_TOOLTIP, new ITooltipProvider() {
public String getTooltip(final ModeController modeController, NodeModel node, Component view) {
if (!ShortenedTextModel.isShortened(node)) {
return null;
}
final NodeStyleController style = (NodeStyleController) modeController.getExtension(NodeStyleController.class);
final Font font = style.getFont(node);
final StringBuilder rule = new StringBuilder();
rule.append("font-family: " + font.getFamily() + ";");
rule.append("font-size: " + font.getSize() + "pt;");
rule.append("margin-top:0;");
if (font.isItalic()) {
rule.append("font-style: italic; ");
}
if (font.isBold()) {
rule.append("font-weight: bold; ");
}
final Color nodeTextColor = view.getForeground();
rule.append("color: ").append(ColorUtils.colorToString(nodeTextColor)).append(";");
final Object data = node.getUserObject();
String text;
try {
text = getTransformedText(data, node, data);
}
catch (Exception e) {
text = TextUtils.format("MainView.errorUpdateText", data, e.getLocalizedMessage());
rule.append("color:red;");
}
if (!HtmlUtils.isHtmlNode(text)) {
text = HtmlUtils.plainToHTML(text);
}
final String tooltipText = text.replaceFirst("<body>", "<body><div style=\"" + rule + "\">")
.replaceFirst("</body>", "</div></body>");
return tooltipText;
}
});
}
public void setIsMinimized(NodeModel node, boolean shortened) {
boolean oldState = ShortenedTextModel.getShortenedTextModel(node) != null;
if(oldState == shortened){
return;
}
if(shortened){
ShortenedTextModel.createShortenedTextModel(node);
}
else{
node.removeExtension(ShortenedTextModel.class);
}
Controller.getCurrentModeController().getMapController().nodeChanged(node, "SHORTENER", oldState, shortened);
}
public void toggleShortened(NodeModel node) {
setIsMinimized(node, ! isMinimized(node));
}
public String getNodeFormat(NodeModel node) {
Collection<IStyle> collection = LogicalStyleController.getController(modeController).getStyles(node);
final MapStyleModel model = MapStyleModel.getExtension(node.getMap());
for(IStyle styleKey : collection){
final NodeModel styleNode = model.getStyleNode(styleKey);
if (styleNode == null) {
continue;
}
final String format = NodeStyleModel.getNodeFormat(styleNode);
if (format != null) {
return format;
}
}
return PatternFormat.STANDARD_FORMAT_PATTERN;
}
public boolean getNodeNumbering(NodeModel node) {
Collection<IStyle> collection = LogicalStyleController.getController(modeController).getStyles(node);
final MapStyleModel model = MapStyleModel.getExtension(node.getMap());
for(IStyle styleKey : collection){
final NodeModel styleNode = model.getStyleNode(styleKey);
if (styleNode == null) {
continue;
}
final Boolean numbering = NodeStyleModel.getNodeNumbering(styleNode);
if (numbering != null) {
return numbering;
}
}
return false;
}
}