/*
Copyright (c) 2009 The Regents of the University of California.
All rights reserved.
Permission is hereby granted, without written agreement and without
license or royalty fees, to use, copy, modify, and distribute this
software and its documentation for any purpose, provided that the above
copyright notice and the following two paragraphs appear in all copies
of this software.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
ENHANCEMENTS, OR MODIFICATIONS..
*/
package org.clothocad.wikieditorpanel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JEditorPane;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.border.Border;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLDocument;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import net.iharder.dnd.FileDrop;
import org.clothocore.api.core.Collator;
import org.clothocore.api.core.Collector;
import org.clothocore.api.data.Attachment;
import org.clothocore.api.data.Attachment.AttachmentType;
import org.clothocore.api.data.ObjBase;
import org.clothocore.api.data.ObjType;
import org.clothocore.api.data.WikiText;
import org.clothocore.api.dnd.ObjBaseObserver;
import org.clothocore.api.dnd.RefreshEvent;
import org.clothocore.util.misc.BareBonesBrowserLaunch;
import org.clothocore.util.panels.CardPanel;
import org.openide.util.ImageUtilities;
/**
* A wikiEditor Panel is a Swing container that is used to view and edit WikiText.
* It will obey whatever postioning and location settings are imposed by the parent
* container. Just treat it like a JPanel, but you have to give it the parent Window
* (most likely a JFrame or JWindow) and the WikiText object in its constructor.
*
* To change the colors, borders, or other visual aspects of it, you can extend
* WikiEditorPanel and change those variable directly (they are protected access).
*
* @author J. Christopher Anderson
*/
public class WikiEditorPanel extends CardPanel {
public WikiEditorPanel(final Window parent, final WikiText wiki_text) {
_wt = wiki_text;
_parent = parent;
initComponents();
createEmptyHTMLPage();
switchModes();
obo = new ObjBaseObserver() {
@Override
public void update(ObjBase obj, RefreshEvent evt) {
if(obj==null) {
return;
}
if(evt.isCondition(RefreshEvent.Condition.WIKITEXT_CHANGED)) {
WikiText wt = (WikiText) obj;
//Refresh the wiki page
String currwiki = wikiArea.getText();
String realwiki = wt.getAsWikiText();
if(!realwiki.equals(currwiki)) {
wikiArea.setText(realwiki);
}
//Refresh the html page
String texty = wikiArea.getText();
if(!texty.equals(_wt.getAsWikiText())) {
_wt.changeTextAsWiki(texty);
}
if(texty.equals("")) {
htmlArea.setDocument(emptyHTMLPage);
htmlArea.setBackground(htmlEmpty);
htmlArea.setForeground(Color.WHITE);
} else {
htmlArea.setDocument(fullHTMLPage);
htmlArea.setText(_wt.getAsHTML());
htmlArea.setForeground(Color.BLACK);
htmlArea.setBackground(htmlBack);
}
//Determine if isChanged or not, change background color
if(!texty.equals(_wt.getAsWikiText())) {
if(!texty.equals("")) {
htmlArea.setBackground(htmlUnsaved);
}
}
}
}
};
_wt.isObservedBy(obo);
}
private void initComponents() {
htmlPanel = new javax.swing.JPanel();
htmlPanel.setLayout(new BorderLayout());
htmlArea = new javax.swing.JEditorPane("text/html", _wt.getAsHTML());
htmlScrollPane = new javax.swing.JScrollPane();
htmlScrollPane.setViewportView(htmlArea);
htmlScrollPane.setBorder(loweredbevel);
htmlArea.setBackground(htmlBack);
htmlArea.setEditable(false);
htmlArea.setToolTipText("Type ctrl-E or double-click to edit, or ctrl-H for help");
htmlArea.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
htmlArea.setFont(new Font("Arial", Font.PLAIN, 12));
fullHTMLPage = (HTMLDocument) htmlArea.getDocument();
htmlArea.addHyperlinkListener(new javax.swing.event.HyperlinkListener() {
@Override
public void hyperlinkUpdate(javax.swing.event.HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
//If it's a Media link (a link to a file)
if(event.getDescription().indexOf("Media:")>0) {
String url = event.getDescription();
int start = url.indexOf("Media:") + 6;
final String fileName = url.substring(start);
new Thread() {
@Override
public void run() {
launchFile(fileName);
}
}.start();
//If it's an ObjBase link
} else if(event.getDescription().indexOf("Link:")>0) {
try {
String url = event.getDescription();
String[] words = url.split(":");
String stype = words[1];
ObjType type = ObjType.valueOf(stype);
String uuid = words[2];
ObjBase obj = Collector.get(type, uuid);
obj.launchDefaultViewer();
} catch(Exception e) {
}
//If it's not media or link, it's probably a URL, launch it in browser
} else {
try {
URL url = new URL(event.getURL().toString());
BareBonesBrowserLaunch.openURL(event.getURL().toString());
} catch (Exception e) {
}
}
}
}});
//Listen for mouse double clicks
htmlArea.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseClicked(java.awt.event.MouseEvent evt) {
//If it was a double-click
if(evt.getClickCount()==2) {
switchModes();
}
}});
//Listen for ctrl-E to switch to wiki mode
htmlArea.getInputMap().put(KeyStroke.getKeyStroke("control E"), "convertToWikiAction");
htmlArea.getActionMap().put("convertToWikiAction",
new AbstractAction("convertToWikiAction") {
@Override
public void actionPerformed(ActionEvent evt) {
switchModes();
}});
//Listen for ctrl-S from either card and save the edited wikitext in response
htmlArea.getInputMap().put(KeyStroke.getKeyStroke("control S"), "saveWikiAction");
htmlArea.getActionMap().put("saveWikiAction",
new AbstractAction("saveWikiAction") {
@Override
public void actionPerformed(ActionEvent evt) {
saveWikiText();
}});
//Listen for ctrl-H from wiki card and launch help
htmlArea.getInputMap().put(KeyStroke.getKeyStroke("control H"), "getHelpAction");
htmlArea.getInputMap().put(KeyStroke.getKeyStroke("F1"), "getHelpAction");
htmlArea.getActionMap().put("getHelpAction",
new AbstractAction("getHelpAction") {
@Override
public void actionPerformed(ActionEvent evt) {
String url = Collator.helpURLBase + "/WikiText_Editor";
BareBonesBrowserLaunch.openURL(url);
}});
htmlPanel.add(htmlScrollPane, BorderLayout.CENTER);
wikiPanel = new javax.swing.JPanel();
wikiPanel.setLayout(new BorderLayout());
wikiScrollPane = new javax.swing.JScrollPane();
wikiArea = new javax.swing.JTextArea();
wikiArea.setLineWrap(true);
wikiArea.setWrapStyleWord(true);
wikiArea.setText(_wt.getAsWikiText());
wikiScrollPane.setViewportView(wikiArea);
wikiScrollPane.setBorder(loweredbevel);
wikiArea.setBackground(wikiBack);
wikiArea.setForeground(wikiTextColor);
wikiArea.setEditable(true);
wikiArea.setToolTipText("Type ctrl-P to return to html view or ctrl-H for help");
wikiArea.setFont(new Font("Monospaced", Font.BOLD, 13));
//When focus is lost update the wikiText object
wikiArea.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
if(_wt==null) {
return;
}
String oldtext = _wt.getAsWikiText();
String newText = wikiArea.getText();
if(!newText.equals(oldtext)) {
_wt.changeTextAsWiki(newText);
}
}
} );
//Listen for ctrl-P to switch to html mode
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control P"), "convertToHtmlAction");
wikiArea.getActionMap().put("convertToHtmlAction",
new AbstractAction("convertToHtmlAction") {
@Override
public void actionPerformed(ActionEvent evt) {
switchModes();
}});
//Listen for ctrl-S from either card and save the edited wikitext in response
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control S"), "saveWikiAction");
wikiArea.getActionMap().put("saveWikiAction",
new AbstractAction("saveWikiAction") {
@Override
public void actionPerformed(ActionEvent evt) {
_wt.saveDefault();
htmlArea.setBackground(htmlBack);
}});
//Listen for ctrl-H from wiki card and launch help
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control H"), "getHelpAction");
wikiArea.getActionMap().put("getHelpAction",
new AbstractAction("getHelpAction") {
@Override
public void actionPerformed(ActionEvent evt) {
String url = Collator.helpURLBase + "/WikiText_Editor";
BareBonesBrowserLaunch.openURL(url);
}});
//Add a file dropped-into-window listener
new FileDrop( wikiArea, new FileDrop.Listener()
{ @Override
public void filesDropped( java.io.File[] files ) {
for(int i=0; i< files.length; i++) {
inputImage(files[i]);
}
}
});
//Listen for ctrl-Shift-v from wiki card and paste image
wikiArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V,InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK), "pasteImageAction");
wikiArea.getActionMap().put("pasteImageAction",
new AbstractAction("pasteImageAction") {
@Override
public void actionPerformed(ActionEvent evt) {
getImageFromClipboard();
}});
//Listen for ctrl-c from wiki card and copy selected text
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control C"), "copyTextAction");
wikiArea.getActionMap().put("copyTextAction",
new AbstractAction("copyTextAction") {
@Override
public void actionPerformed(ActionEvent evt) {
String selected = wikiArea.getSelectedText();
StringSelection ss = new StringSelection(selected);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
}});
//Listen for ctrl-x from wiki card and cut selected text
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control X"), "cutTextAction");
wikiArea.getActionMap().put("cutTextAction",
new AbstractAction("cutTextAction") {
@Override
public void actionPerformed(ActionEvent evt) {
new SwingWorker() {
//VARIABLES:
String out;
@Override
protected Object doInBackground() throws Exception {
String selected = wikiArea.getSelectedText();
String original = wikiArea.getText();
int start = original.indexOf(selected);
out = original.substring(0,start) + original.substring(start+selected.length());
StringSelection ss = new StringSelection(selected);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
return null;
}
@Override
protected void done() {
_wt.changeTextAsWiki(out);
}
}.execute();
}});
//Listen for ctrl-v from wiki card and paste text
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control V"), "pasteTextAction");
wikiArea.getActionMap().put("pasteTextAction",
new AbstractAction("pasteTextAction") {
@Override
public void actionPerformed(ActionEvent evt) {
new SwingWorker() {
@Override
protected Object doInBackground() throws Exception {
String original = wikiArea.getText();
int start = wikiArea.getCaretPosition();
String out = original.substring(0,start) + getClipboardString() + original.substring(start);
_wt.changeTextAsWiki(out);
return null;
}
}.execute();
}});
//<<<<<<<<< Section on Undo/Redo for WikiArea >>>>>>>>>
final UndoManager undo = new UndoManager();
Document doc = wikiArea.getDocument();
// Listen for undo and redo events
doc.addUndoableEditListener(new UndoableEditListener() {
@Override
public void undoableEditHappened(UndoableEditEvent evt) {
undo.addEdit(evt.getEdit());
}
});
// Create an undo action and add it to the text component
wikiArea.getActionMap().put("Undo",
new AbstractAction("Undo") {
@Override
public void actionPerformed(ActionEvent evt) {
try {
if (undo.canUndo()) {
undo.undo();
}
} catch (CannotUndoException e) {
}
}
});
// Bind the undo action to ctl-Z
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo");
// Create a redo action and add it to the text component
wikiArea.getActionMap().put("Redo",
new AbstractAction("Redo") {
@Override
public void actionPerformed(ActionEvent evt) {
try {
if (undo.canRedo()) {
undo.redo();
}
} catch (CannotRedoException e) {
}
}
});
// Bind the redo action to ctl-Y
wikiArea.getInputMap().put(KeyStroke.getKeyStroke("control Y"), "Redo");
//<<<<<<<<< END UNDO/REDO >>>>>>>>>
wikiPanel.add(wikiScrollPane, BorderLayout.CENTER);
add(htmlPanel);
add(wikiPanel);
}
public void saveWikiText() {
obo.update(_wt, new RefreshEvent(_wt, RefreshEvent.Condition.WIKITEXT_CHANGED));
_wt.saveDefault();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
htmlArea.setBackground(htmlBack);
}
});
setHTMLMode();
}
public void clear() {
_wt = new WikiText("");
_wt.setTransient();
setHTMLMode();
}
public WikiText getWikiText() {
return _wt;
}
private void createEmptyHTMLPage() {
//File file = new File(Attachment.cacheDir.getAbsolutePath() + "\\" + "pencilandpaper.png");
File file = new File(Attachment.cacheDir.getAbsolutePath() + File.separator + "pencilandpaper.png");
if(!file.exists()) {
BufferedImage img = (BufferedImage) ImageUtilities.loadImage("org/clothocad/wikieditorpanel/pencilandpaper.png", false);
if(img!=null) {
try {
ImageIO.write(img, "png", file);
} catch (IOException ex) {
}
}
}
String imagelink = file.getAbsolutePath();
JEditorPane pane = new JEditorPane("text/html", "<html>\n<body>\n<p align=\"center\"> </p>\n<p align=\"center\"> </p>\n<p align=\"center\"><img src=\"file:\\" + File.separator + imagelink + "\" width=\"110\" height=\"87\" alt=\"doubleClick\" /></p>\n<p align=\"center\"> </p>\n<p align=\"center\" class=\"style1\">Double click to add text.</p>\n</body>\n</html>");
pane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
pane.setFont(new Font("Arial", Font.PLAIN, 12));
pane.setBackground(htmlEmpty);
pane.setForeground(new Color(255,255,255));
emptyHTMLPage = (HTMLDocument) pane.getDocument();
}
public void setHTMLMode() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
showCard(htmlPanel);
htmlArea.requestFocus();
_editMode = false;
}
});
}
public void setEditMode() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
showCard(wikiPanel);
wikiArea.requestFocus();
wikiArea.setCaretPosition(wikiArea.getText().length());
_editMode = true;
}
});
}
private void switchModes() {
if(_editMode) {
setHTMLMode();
} else {
setEditMode();
}
_editMode = !_editMode;
}
/**
* Download and launch the file associated with the WikiText
* that is named filename
*
* @param filename String of the file Name in the WikiText
*/
public static void launchFile(String filename) {
File afile = new File("cache\\" + filename);
String end = filename.split("\\.")[1];
if(!afile.exists()) {
System.out.println("File " + afile.getAbsolutePath() +" not there");
return;
}
try {
String[] commands = {"cmd", "/c", "start", "\"DummyTitle\"", afile.getAbsolutePath()};
Runtime.getRuntime().exec(commands);
} catch (IOException ex) {
}
}
/**
* Starting with a File, turn it into a saved file and WikiText flag
* @param file
*/
private void inputImage(File file) {
String filepath = file.getName();
if(filepath.endsWith(".png") || filepath.endsWith(".PNG") ) {
Attachment att = new Attachment(file, file.getName(), AttachmentType.PNG);
_wt.addObject(att);
_wt.appendTextAsWiki("[[Image:" + filepath + "]]");
return;
}
if(filepath.endsWith(".jpg") || filepath.endsWith(".JPG") || filepath.endsWith(".gif") || filepath.endsWith(".GIF") ) {
//CONVERT THEM TO PNG THEN MAKE ATTACHMENT AND ADD IT
return;
}
if(filepath.endsWith(".xls") || filepath.endsWith(".xlsx") || filepath.endsWith(".csv") ) {
Attachment att = new Attachment(file, file.getName(), AttachmentType.EXCEL);
_wt.addObject(att);
_wt.appendTextAsWiki("[[Media:" + filepath + "]]");
return;
}
if(filepath.endsWith(".gb") ) {
Attachment att = new Attachment(file, file.getName(), AttachmentType.GB);
_wt.addObject(att);
_wt.appendTextAsWiki("[[Media:" + filepath + "]]");
return;
}
if(filepath.endsWith(".pdf") ) {
Attachment att = new Attachment(file, file.getName(), AttachmentType.PDF);
_wt.addObject(att);
_wt.appendTextAsWiki("[[Media:" + filepath + "]]");
return;
}
if(filepath.endsWith(".ab1") ) {
Attachment att = new Attachment(file, file.getName(), AttachmentType.ABI);
_wt.addObject(att);
_wt.appendTextAsWiki("[[Media:" + filepath + "]]");
return;
}
Attachment att = new Attachment(file, file.getName(), AttachmentType.OTHER);
_wt.addObject(att);
_wt.appendTextAsWiki("[[Media:" + filepath + "]]");
return;
}
/**
* Get an image off the system clipboard.
* @return returns the image if successful, otherwise null
*/
private boolean getImageFromClipboard() {
//Request filename from user
String pngname = JOptionPane.showInputDialog("What do you want to call this image?");
if(pngname==null) {
return false;
}
String filename = Attachment.cacheDir.getAbsolutePath() + "\\" + pngname + ".png";
File localfile = new File(filename);
File tempfile = new File(Attachment.cacheDir.getAbsolutePath() + "\\temporary11111.png");
//Pull image off the clipboard and save to file
Transferable content = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
if(content==null){
System.err.println("error: nothing found in clipboard");
return false;
}
if(!content.isDataFlavorSupported(DataFlavor.imageFlavor)){
System.err.println("error: no image found in clipbaord");
return false;
}
try {
BufferedImage img = (BufferedImage) content.getTransferData(DataFlavor.imageFlavor);
ImageIO.write(img,"png",tempfile);
} catch(Exception e) {
e.printStackTrace();
return false;
}
System.out.println("WEP image copied to: " + tempfile.getAbsolutePath());
//Make the Attachment and put in wikitext
Attachment att = new Attachment(tempfile, localfile.getName(), AttachmentType.PNG);
_wt.addObject(att);
_wt.appendTextAsWiki("[[Image:" + localfile.getName() + "]]");
tempfile.delete();
return true;
}
/**
* Pull a String from the system clipboard
* @return The String form the clipboard
*/
private String getClipboardString() {
String result = "";
Transferable contents = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
boolean hasTransferableText =
(contents != null) &&
contents.isDataFlavorSupported(DataFlavor.stringFlavor)
;
if ( hasTransferableText ) {
try {
result = (String)contents.getTransferData(DataFlavor.stringFlavor);
}
catch (UnsupportedFlavorException ex){
System.out.println(ex);
}
catch (IOException ex) {
System.out.println(ex);
}
}
return result;
}
public void removeHtmlBorders() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
htmlArea.setBorder(null);
htmlScrollPane.setBorder(null);
htmlPanel.setBorder(null);
}
});
}
public void removeWikiBorders() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
wikiArea.setBorder(null);
wikiScrollPane.setBorder(null);
wikiPanel.setBorder(null);
}
});
}
public JEditorPane getTopContainer() {
return htmlArea;
}
/*-----------------
variables
-----------------*/
protected WikiText _wt;
protected ObjBaseObserver obo;
private boolean _editMode = false;
private HTMLDocument emptyHTMLPage;
private HTMLDocument fullHTMLPage;
protected Border loweredbevel = BorderFactory.createLoweredBevelBorder();
protected javax.swing.JComponent _myContainer;
protected javax.swing.JTextArea wikiArea;
protected javax.swing.JEditorPane htmlArea;
protected javax.swing.JScrollPane htmlScrollPane;
protected javax.swing.JScrollPane wikiScrollPane;
protected javax.swing.JPanel wikiPanel;
protected javax.swing.JPanel htmlPanel;
private Window _parent;
protected Color wikiBack = new Color(224, 223, 211);
protected Color htmlBack = new Color(255, 255, 255);
protected Color htmlEmpty = new Color(35, 48, 64);
protected Color wikiTextColor = new Color(32, 123, 214);
protected Color htmlUnsaved = new Color(242, 230, 208);
}