/* * 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.clipboard; import java.awt.Color; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.freeplane.core.extension.IExtension; import org.freeplane.core.util.LogUtils; import org.freeplane.features.link.NodeLinks; import org.freeplane.features.map.IMapSelection; import org.freeplane.features.map.MapWriter.Mode; import org.freeplane.features.map.NodeModel; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.features.nodestyle.NodeStyleModel; import org.freeplane.features.text.TextController; import org.freeplane.features.url.UrlManager; /** * @author Dimitry Polivaev */ public class ClipboardController implements IExtension { public static final String NODESEPARATOR = "<nodeseparator>"; public static ClipboardController getController() { return (ClipboardController) Controller.getCurrentModeController().getExtension(ClipboardController.class); } public static void install( final ClipboardController clipboardController) { Controller.getCurrentModeController().addExtension(ClipboardController.class, clipboardController); } final private Clipboard clipboard; // final private ModeController modeController; final private Clipboard selection; public ClipboardController() { super(); // this.modeController = modeController; final Toolkit toolkit = Toolkit.getDefaultToolkit(); selection = toolkit.getSystemSelection(); clipboard = toolkit.getSystemClipboard(); createActions(); } private void collectColors(final NodeModel node, final HashSet<Color> colors) { final Color color = NodeStyleModel.getColor(node); if (color != null) { colors.add(color); } for (final NodeModel child : Controller.getCurrentModeController().getMapController().childrenUnfolded(node)) { collectColors(child, colors); } } public Transferable copy(final Collection<NodeModel> selectedNodes, final boolean copyInvisible) { try { final String forNodesFlavor = createForNodesFlavor(selectedNodes, copyInvisible); final String plainText = getAsPlainText(selectedNodes); return new MindMapNodesSelection(forNodesFlavor, plainText, getAsRTF(selectedNodes), getAsHTML(selectedNodes), null, getAsFileList(selectedNodes)); } catch (final UnsupportedFlavorException ex) { LogUtils.severe(ex); } catch (final IOException ex) { LogUtils.severe(ex); } return null; } private List<?> getAsFileList(Collection<NodeModel> selectedNodes) { List<File> fileList = new ArrayList<File>(); for(NodeModel node : selectedNodes){ URI uri = NodeLinks.getValidLink(node); if(uri == null) continue; try{ // uri with scheme is always "absolute" ( so are workspace relative links) if(!uri.isAbsolute()){ final UrlManager urlManager = (UrlManager) Controller.getCurrentModeController().getExtension(UrlManager.class); if(node.getMap() == null || urlManager == null) continue; if(uri.getScheme() == null ||uri.getScheme().equals("")) uri = urlManager.getAbsoluteUri(node.getMap(), uri); } if(uri.getScheme().equals("file")){ fileList.add(new File(uri)); } else{ fileList.add(new File(uri.toURL().openConnection().getURL().toURI())); } } catch(IllegalArgumentException e){ continue; } catch (MalformedURLException e) { continue; } catch (URISyntaxException e) { continue; } catch (IOException e) { continue; } } return fileList; } public Transferable copy(final IMapSelection selection) { return copy(selection.getSortedSelection(true), false); } public Transferable copy(final NodeModel node, final boolean saveInvisible) { final StringWriter stringWriter = new StringWriter(); try { Controller.getCurrentModeController().getMapController().getMapWriter().writeNodeAsXml(stringWriter, node, Mode.CLIPBOARD, saveInvisible, true, false); } catch (final IOException e) { LogUtils.severe(e); } return new MindMapNodesSelection(stringWriter.toString(), null, null, null, null, null); } public Transferable copySingle(final Collection<NodeModel> source) { final int size = source.size(); final Vector<NodeModel> target = new Vector<NodeModel>(size); target.setSize(size); int i = size - 1; for (NodeModel node : source) { target.set(i, new SingleCopySource(node)); i--; } return copy(target, false); } /** * */ private void createActions() { final Controller controller = Controller.getCurrentController(); ModeController modeController = controller.getModeController(); modeController.addAction(new CopyAction()); modeController.addAction(new CopySingleAction()); modeController.addAction(new CopyIDAction()); modeController.addAction(new CopyNodeURIAction()); } public String createForNodesFlavor(final Collection<NodeModel> selectedNodes, final boolean copyInvisible) throws UnsupportedFlavorException, IOException { String forNodesFlavor = ""; boolean firstLoop = true; for (final NodeModel tmpNode : selectedNodes) { if (firstLoop) { firstLoop = false; } else { forNodesFlavor += "<nodeseparator>"; } forNodesFlavor += copy(tmpNode, copyInvisible).getTransferData(MindMapNodesSelection.mindMapNodesFlavor); } return forNodesFlavor; } public String getAsHTML(final Collection<NodeModel> selectedNodes) { try { final StringWriter stringWriter = new StringWriter(); final BufferedWriter fileout = new BufferedWriter(stringWriter); writeHTML(selectedNodes, fileout); fileout.close(); return stringWriter.toString(); } catch (final Exception e) { LogUtils.severe(e); return null; } } public String getAsPlainText(final Collection<NodeModel> selectedNodes) { try { final StringWriter stringWriter = new StringWriter(); final BufferedWriter fileout = new BufferedWriter(stringWriter); for (final Iterator<NodeModel> it = selectedNodes.iterator(); it.hasNext();) { writeTXT(it.next(), fileout,/* depth= */0); } fileout.close(); return stringWriter.toString(); } catch (final Exception e) { LogUtils.severe(e); return null; } } public String getAsRTF(final Collection<NodeModel> selectedNodes) { try { final StringWriter stringWriter = new StringWriter(); final BufferedWriter fileout = new BufferedWriter(stringWriter); writeRTF(selectedNodes, fileout); fileout.close(); return stringWriter.toString(); } catch (final Exception e) { LogUtils.severe(e); return null; } } /** */ public Transferable getClipboardContents() { return clipboard.getContents(this); } private String rtfEscapeUnicodeAndSpecialCharacters(final String text) { final int len = text.length(); final StringBuilder result = new StringBuilder(len); int intValue; char myChar; for (int i = 0; i < len; ++i) { myChar = text.charAt(i); intValue = text.charAt(i); if (intValue > 128) { result.append("\\u").append(intValue).append("?"); } else { switch (myChar) { case '\\': result.append("\\\\"); break; case '{': result.append("\\{"); break; case '}': result.append("\\}"); break; case '\n': result.append(" \\line "); break; default: result.append(myChar); } } } return result.toString(); } public void saveHTML(final NodeModel rootNodeOfBranch, final File file) throws IOException { final BufferedWriter fileout = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); final MindMapHTMLWriter htmlWriter = new MindMapHTMLWriter(Controller.getCurrentModeController().getMapController(), fileout); htmlWriter.writeHTML(rootNodeOfBranch); } public boolean saveTXT(final NodeModel rootNodeOfBranch, final File file) { try { final BufferedWriter fileout = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); writeTXT(rootNodeOfBranch, fileout,/* depth= */ 0); fileout.close(); return true; } catch (final Exception e) { LogUtils.severe("Error in MindMapMapModel.saveTXT(): ", e); return false; } } /** */ public void setClipboardContents(final Transferable t) { clipboard.setContents(t, null); if (selection != null) { selection.setContents(t, null); } } /** copies a string to the system clipboard. */ public void setClipboardContents(final String string) { setClipboardContents(new StringSelection(string)); } public NodeModel duplicate(final NodeModel source, boolean withChildren) { try { final StringWriter writer = new StringWriter(); ModeController modeController = Controller.getCurrentModeController(); modeController.getMapController().getMapWriter() .writeNodeAsXml(writer, source, Mode.CLIPBOARD, true, withChildren, false); final String result = writer.toString(); final NodeModel copy = modeController.getMapController().getMapReader().createNodeTreeFromXml( source.getMap(), new StringReader(result), Mode.CLIPBOARD); copy.setFolded(false); return copy; } catch (final Exception e) { LogUtils.severe(e); return null; } } private void writeChildrenRTF(final NodeModel node, final Writer fileout, final int depth, final HashMap<Color, Integer> colorTable) throws IOException { for (final NodeModel child : Controller.getCurrentModeController().getMapController().childrenUnfolded(node)) { if (child.isVisible()) { writeRTF(child, fileout, depth + 1, colorTable); } else { writeChildrenRTF(child, fileout, depth, colorTable); } } } private void writeChildrenText(final NodeModel node, final Writer fileout, final int depth) throws IOException { for (final NodeModel child : Controller.getCurrentModeController().getMapController().childrenUnfolded(node)) { if (child.isVisible()) { writeTXT(child, fileout, depth + 1); } else { writeChildrenText(child, fileout, depth); } } } public void writeHTML(final Collection<NodeModel> selectedNodes, final Writer fileout) throws IOException { final MindMapHTMLWriter htmlWriter = new MindMapHTMLWriter(Controller.getCurrentModeController().getMapController(), fileout); htmlWriter.writeHTML(selectedNodes); } public boolean writeRTF(final Collection<NodeModel> selectedNodes, final BufferedWriter fileout) { try { final HashSet<Color> colors = new HashSet<Color>(); for (final Iterator<NodeModel> it = selectedNodes.iterator(); it.hasNext();) { collectColors(it.next(), colors); } String colorTableString = "{\\colortbl;\\red0\\green0\\blue255;"; final HashMap<Color, Integer> colorTable = new HashMap<Color, Integer>(); int colorPosition = 2; for (final Color color : colors) { colorTableString += "\\red" + color.getRed() + "\\green" + color.getGreen() + "\\blue" + color.getBlue() + ";"; colorTable.put(color, new Integer(colorPosition)); } colorTableString += "}"; fileout.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Arial;}" + colorTableString + "}" + "\\viewkind4\\uc1\\pard\\f0\\fs20{}"); for (final Iterator<NodeModel> it = selectedNodes.iterator(); it.hasNext();) { writeRTF(it.next(), fileout,/* depth= */0, colorTable); } fileout.write("}"); return true; } catch (final Exception e) { LogUtils.severe(e); return false; } } public void writeRTF(final NodeModel mindMapNodeModel, final Writer fileout, final int depth, final HashMap<Color, Integer> colorTable) throws IOException { String pre = "{" + "\\li" + depth * 350; String level; if (depth <= 8) { level = "\\outlinelevel" + depth; } else { level = ""; } String fontsize = ""; if (NodeStyleModel.getColor(mindMapNodeModel) != null) { pre += "\\cf" + colorTable.get(NodeStyleModel.getColor(mindMapNodeModel)).intValue(); } final NodeStyleModel font = NodeStyleModel.getModel(mindMapNodeModel); if (font != null) { if (Boolean.TRUE.equals(font.isItalic())) { pre += "\\i "; } if (Boolean.TRUE.equals(font.isBold())) { pre += "\\b "; } if (font.getFontSize() != null) { fontsize = "\\fs" + Math.round(1.5 * font.getFontSize()); pre += fontsize; } } pre += "{}"; fileout.write("\\li" + depth * 350 + level + "{}"); final String nodeText = TextController.getController().getPlainTextContent(mindMapNodeModel); if (nodeText.matches(" *")) { fileout.write("o"); } else { final String text = rtfEscapeUnicodeAndSpecialCharacters(nodeText); if (NodeLinks.getValidLink(mindMapNodeModel) != null) { final String link = rtfEscapeUnicodeAndSpecialCharacters(NodeLinks.getLinkAsString(mindMapNodeModel)); if (link.equals(nodeText)) { fileout.write(pre + "<{\\ul\\cf1 " + link + "}>" + "}"); } else { fileout.write("{" + fontsize + pre + text + "} "); fileout.write("<{\\ul\\cf1 " + link + "}}>"); } } else { fileout.write(pre + text + "}"); } } fileout.write("\\par"); fileout.write("\n"); writeChildrenRTF(mindMapNodeModel, fileout, depth, colorTable); } public void writeTXT(final NodeModel mindMapNodeModel, final Writer fileout, final int depth) throws IOException { final String plainTextContent = TextController.getController().getPlainTextContent(mindMapNodeModel).replace('\n', ' '); for (int i = 0; i < depth; ++i) { fileout.write(" "); } if (plainTextContent.matches(" *")) { fileout.write("o"); } else { if (NodeLinks.getValidLink(mindMapNodeModel) != null) { final String link = NodeLinks.getLinkAsString(mindMapNodeModel); if (!link.equals(plainTextContent)) { fileout.write(plainTextContent + " "); } fileout.write("<" + link + ">"); } else { fileout.write(plainTextContent); } } fileout.write("\n"); writeChildrenText(mindMapNodeModel, fileout, depth); } }