/* * Copyright (C) 2004 The Concord Consortium, Inc., * 10 Concord Crossing, Concord, MA 01742 * * Web Site: http://www.concord.org * Email: info@concord.org * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * END LICENSE */ package org.concord.swing; import java.awt.BorderLayout; import java.awt.Insets; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextPane; import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.CDATASection; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; public class PageView extends JPanel implements HyperlinkListener, SwingConstants { private static final long serialVersionUID = 1L; protected JToolBar toolBar = new JToolBar(JToolBar.VERTICAL); protected JMenuBar menuBar = new JMenuBar(); protected JFileChooser imageFileChooser = new JFileChooser(); protected JFileChooser componentFileChooser = new JFileChooser(); protected JFileChooser openFileChooser = new JFileChooser(); protected JFileChooser saveFileChooser = new JFileChooser(); protected Editor editor; protected boolean editable = true; protected File currentOpenFile; protected JScrollPane scrollPane; protected JFrame viewFrame; protected PageView viewPage; protected Map actionMap = new HashMap(); protected Map menuMap = new HashMap(); protected Action insertImage = new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent event) { if (imageFileChooser.showOpenDialog(editor) == JFileChooser.APPROVE_OPTION) { File imageFile = imageFileChooser.getSelectedFile(); try { URL iconURL = imageFile.toURL(); editor.insertIcon(iconURL); } catch (Exception e) { System.out.println("" + e); } } } }; protected Action insertComponent = new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent event) { if (componentFileChooser.showOpenDialog(editor) == JFileChooser.APPROVE_OPTION) { File componentFile = componentFileChooser.getSelectedFile(); try { URL componentURL = componentFile.toURL(); editor.insertComponent(componentURL); } catch (Exception e) { System.out.println("" + e); } } } }; protected Action saveAction = new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent event) { if (saveFileChooser.showSaveDialog(editor) == JFileChooser.APPROVE_OPTION) { File file = saveFileChooser.getSelectedFile(); try { OutputStream output = new FileOutputStream(file); editor.write(output); output.close(); } catch (Exception e) { System.out.println("" + e); } } } }; protected Action openAction = new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent event) { if (openFileChooser.showOpenDialog(editor) == JFileChooser.APPROVE_OPTION) { File file = openFileChooser.getSelectedFile(); try { currentOpenFile = file; InputStream input = new FileInputStream(file); editor.read(input, null); input.close(); } catch (Exception e) { System.out.println("" + e); } } } }; protected Action viewAction = new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent event) { if (currentOpenFile != null) { if (viewPage == null) { viewFrame = new JFrame("PageView"); viewPage = new PageView(); viewFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); viewFrame.getContentPane().add(viewPage); viewFrame.setBounds(100, 100, 800, 600); } } try { viewPage.setEditable(true); viewPage.readInput(new FileInputStream(currentOpenFile)); viewPage.setEditable(false); } catch (FileNotFoundException e) { } viewFrame.setVisible(true); } }; protected String [][] commands = { { "OpenAction", "Open", null, "Actions", "images/open.gif" }, { "SaveAction", "Save", null, "Actions", "images/save.gif" }, { "ViewAction", "View", null, "Actions", null }, { "copy-to-clipboard", "Copy", null, "Edit", "images/copy.gif" }, { "cut-to-clipboard", "Cut", null, "Edit", "images/cut.gif" }, { "paste-from-clipboard", "Paste", null, "Edit", "images/paste.gif" }, { "separator", null, null, "Edit", null }, { "select-all", "Select All", null, "Edit", null }, { "left-justify", "Left", "Edit", "Align", "images/AlignLeft.gif" }, { "center-justify", "Center", "Edit", "Align", "images/AlignCenter.gif" }, { "right-justify", "Right", "Edit", "Align", "images/AlignRight.gif" }, { "font-size-8", "8", "Font", "Size", null }, { "font-size-10", "10", "Font", "Size", null }, { "font-size-12", "12", "Font", "Size", null }, { "font-size-14", "14", "Font", "Size", null }, { "font-size-16", "16", "Font", "Size", null }, { "font-size-18", "18", "Font", "Size", null }, { "font-size-24", "24", "Font", "Size", null }, { "font-size-36", "36", "Font", "Size", null }, { "font-size-48", "48", "Font", "Size", null }, { "font-family-Serif", "Serif", "Font", "Family", null }, { "font-family-SansSerif", "SansSerif", "Font", "Family", null }, { "font-family-Monospaced", "Monospaced", "Font", "Family", null }, { "font-bold", "Bold", "Font", "Style", "images/Bold.gif" }, { "font-italic", "Italic", "Font", "Style", "images/Italic.gif" }, { "font-underline", "Underline", "Font", "Style", "images/Underline.gif" }, { "InsertImage", "Image", null, "Insert", "images/InsertPicture.gif" }, { "InsertComponent", "Component", null, "Insert", "images/InsertComponent.gif" }, }; public PageView() { this(true); } public PageView(boolean editable) { setLayout(new BorderLayout()); editor = new Editor(editable); scrollPane = new JScrollPane(editor); initializeCommands(); setEditable(editable); } protected void initializeCommands() { actionMap.put("InsertImage", insertImage); actionMap.put("InsertComponent", insertComponent); actionMap.put("OpenAction", openAction); actionMap.put("SaveAction", saveAction); actionMap.put("ViewAction", viewAction); Action [] actions = editor.getEditorKit().getActions(); for (int i = 0; i < actions.length; i++) { String name = actions[i].getValue(Action.NAME).toString(); actionMap.put(name, actions[i]); } for (int i = 0; i < commands.length; i++) { String [] command = commands[i]; Action action = (Action) actionMap.get(command[0]); URL imageURL = null; JMenu parentMenu = null; JMenu menu = null; JMenuItem item = null; if (command[2] != null) { parentMenu = (JMenu) menuMap.get(command[2]); if (parentMenu == null) { parentMenu = new JMenu(command[2]); menuMap.put(command[2], parentMenu); menuBar.add(parentMenu); } } menu = (JMenu) menuMap.get(command[3]); if (menu == null) { menu = new JMenu(command[3]); menuMap.put(command[3], menu); if (parentMenu != null) parentMenu.add(menu); else menuBar.add(menu); } if (command[1] != null) { item = menu.add(action); item.setText(command[1]); } if (command[4] != null) { imageURL = this.getClass().getResource(command[4]); JButton button = toolBar.add(action); button.setText(null); button.setIcon(new ImageIcon(imageURL)); if (item != null) item.setIcon(button.getIcon()); } if (command[0].equals("separator")) { menu.addSeparator(); } } toolBar.setFloatable(true); add(scrollPane, "Center"); } public void readInput(InputStream input) { editor.read(input, null); try { input.close(); } catch (IOException e) { } } public boolean isEditable() { return editable; } public void setEditable(boolean value) { editable = value; if (editable) { add(toolBar, "West"); add(menuBar, "North"); } else { remove(toolBar); remove(menuBar); } editor.setEditable(editable); validate(); } public void hyperlinkUpdate(HyperlinkEvent event) { } public static void main(String [] args) { JFrame frame = new JFrame("PageView"); PageView view = new PageView(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(view); frame.setBounds(100, 100, 800, 600); frame.setVisible(true); } public static class StyleMethods { protected Map getters = new HashMap(); protected Map setters = new HashMap(); protected final String SET = "set"; protected final String GET = "get"; protected final String IS = "is"; protected final String FONT = "font"; protected final Class [][] primitives = { { Integer.TYPE, Integer.class }, { Long.TYPE, Long.class }, { Short.TYPE, Short.class }, { Byte.TYPE, Byte.class }, { Float.TYPE, Float.class }, { Double.TYPE, Double.class }, { Character.TYPE, Character.class }, { Boolean.TYPE, Boolean.class } }; protected Map typeMap = new HashMap(); public StyleMethods() { Method [] methods = StyleConstants.class.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; String methodName = method.getName(); if (methodName.startsWith(GET)) { createProperty(methodName, GET.length(), method, getters); } else if (methodName.startsWith(IS)) { createProperty(methodName, IS.length(), method, getters); } else if (methodName.startsWith(SET)) { createProperty(methodName, SET.length(), method, setters); } } for (int i = 0; i < primitives.length; i++) { typeMap.put(primitives[i][0], primitives[i][1]); } } protected Class getTypeClass(Class type) { Class typeClass = (Class) typeMap.get(type); if (typeClass == null) return type; return typeClass; } protected void createProperty(String methodName, int prefixLength, Method method, Map table) { String propertyName = methodName.substring(prefixLength); propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1); table.put(propertyName, method); if (propertyName.startsWith(FONT)) { propertyName = propertyName.substring(FONT.length()); propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1); table.put(propertyName, method); } } public Object get(AttributeSet attributeSet, String propertyName) { propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1); Method method = (Method) getters.get(propertyName); if (method != null) { try { return method.invoke(null, new Object [] { attributeSet }); } catch (IllegalArgumentException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (InvocationTargetException e) { System.out.println(e); } } return null; } public void set(MutableAttributeSet attributeSet, String propertyName, Object value) { propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1); Method method = (Method) setters.get(propertyName); if (method != null) { try { if (value instanceof String) { Class [] argClasses = method.getParameterTypes(); Class argType = getTypeClass(argClasses[1]); Constructor constructor = argType.getConstructor(new Class [] { String.class }); value = constructor.newInstance(new Object [] { value }); } method.invoke(null, new Object [] { attributeSet, value }); } catch (IllegalArgumentException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (InvocationTargetException e) { System.out.println(e); } catch (NoSuchMethodException e) { System.out.println(e); } catch (InstantiationException e) { System.out.println(e); } } } } public static class Editor extends JTextPane implements SelectableContainer, CaretListener { private static final long serialVersionUID = 1L; protected DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); protected DocumentBuilder builder; protected StyleContext styleContext = StyleContext.getDefaultStyleContext(); protected Style sectionStyle = styleContext.addStyle("Section", null); protected Style paragraphStyle = styleContext.addStyle("Paragraph", sectionStyle); protected Style contentStyle = styleContext.addStyle("Content", paragraphStyle); protected StyleMethods styleMethods = new StyleMethods(); protected Map componentMap = new HashMap(); protected Map iconMap = new HashMap(); protected Map locationMap = new HashMap(); protected SelectionManager selectionManager = new SelectionManager(); protected ComponentFramework state = new DefaultComponentFramework(); public Editor(boolean editable) { builderFactory.setValidating(false); setMargin(new Insets(5,5,5,5)); super.setEditable(true); selectionManager.setSelectableContainer(this); addCaretListener(this); try { builder = builderFactory.newDocumentBuilder(); } catch (Exception e) { System.out.println(e); } } public void setEditable(boolean value) { super.setEditable(value); if (componentMap != null) { Iterator components = componentMap.keySet().iterator(); while (components.hasNext()) { PageComponent pageComponent = (PageComponent) components.next(); pageComponent.setEditable(value); } } } public void insertIcon(URL iconURL) { ImageIcon icon = new ImageIcon(iconURL); super.insertIcon(icon); iconMap.put(icon, iconURL); } public void insertComponent(URL componentURL) { PageComponent component = new PageComponent(componentURL); component.setSelectionManager(selectionManager); super.insertComponent(component); componentMap.put(component, componentURL); } public void write(OutputStream outputStream) { AbstractDocument doc = (AbstractDocument) getDocument(); Element root = doc.getDefaultRootElement(); Document document = builder.newDocument(); writeTree(root, document, document); try { Transformer t = TransformerFactory.newInstance().newTransformer(); t.setOutputProperty(OutputKeys.INDENT,"yes"); t.setOutputProperty(OutputKeys.METHOD, "xml"); t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes"); t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","4"); t.transform(new DOMSource(document),new StreamResult(outputStream)); } catch (Exception e) { System.out.println("write = " + e); } } protected void writeElement(Element element, Document document, Node parent, Object key, Map map) { AttributeSet attributes = element.getAttributes(); Object value = attributes.getAttribute(key); URL url = (URL) map.get(value); org.w3c.dom.Element xmlElement = document.createElement(key.toString()); parent.appendChild(xmlElement); xmlElement.setAttribute("href", url.toString()); xmlElement.setAttribute("start", "" + element.getStartOffset()); xmlElement.setAttribute("end", "" + element.getEndOffset()); } protected void writeIconElement(Element element, Document document, Node parent) { Object iconKey = StyleConstants.CharacterConstants.IconAttribute; writeElement(element, document, parent, iconKey, iconMap); } protected void writeComponentElement(Element element, Document document, Node parent) { Object componentKey = StyleConstants.CharacterConstants.ComponentAttribute; writeElement(element, document, parent, componentKey, componentMap); } protected void writeTree(Element element, Document document, Node parent) { String elementName = element.getName(); if (elementName.equals("icon")) writeIconElement(element, document, parent); else if (elementName.equals("component")) writeComponentElement(element, document, parent); else { org.w3c.dom.Element xmlElement = document.createElement(elementName); parent.appendChild(xmlElement); if (parent == document) { try { StringWriter writer = new StringWriter(); super.write(writer); String data = writer.toString(); Node dataNode = document.createCDATASection(data); document.getDocumentElement().appendChild(dataNode); } catch (Exception e) { System.out.println("writeData = " + e); } } Enumeration eNames = ((AttributeSet) element).getAttributeNames(); while (eNames.hasMoreElements()) { Object name = eNames.nextElement(); if (name.toString().equals("resolver")) continue; Object value = ((AttributeSet) element).getAttribute(name); xmlElement.setAttribute(name.toString(), value.toString()); } if (element.isLeaf()) { xmlElement.setAttribute("start", "" + element.getStartOffset()); xmlElement.setAttribute("end", "" + element.getEndOffset()); } else { for (int i = 0; i < element.getElementCount(); i++) { Element childElement = element.getElement(i); writeTree(childElement, document, xmlElement); } } } } protected void installComponents() { Iterator componentUrls = locationMap.keySet().iterator(); while (componentUrls.hasNext()) { String urlString = (String) componentUrls.next(); Integer location = (Integer) locationMap.get(urlString); int start = location.intValue(); DefaultStyledDocument doc = (DefaultStyledDocument) getDocument(); try { URL componentURL = new URL(urlString); doc.remove(start, 1); int dot = getCaret().getDot(); getCaret().setDot(start); insertComponent(componentURL); getCaret().setDot(dot); } catch (MalformedURLException e) { } catch (BadLocationException e) { } } } public void read(InputStream input, Object desc) { removeCaretListener(this); try { Document document = builder.parse(new InputSource(input)); readTree(document, document); installComponents(); } catch (Exception e) { } addCaretListener(this); } protected void readDocumentElement(Node node, Style style) { style.removeAttributes(style); NamedNodeMap attributes = node.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node attributeNode = attributes.item(i); String nodeName = attributeNode.getNodeName(); Object nodeValue = attributeNode.getNodeValue(); styleMethods.set(style, nodeName, nodeValue); } } protected void readContentElement(Node node) { contentStyle.removeAttributes(contentStyle); NamedNodeMap attributes = node.getAttributes(); Node attributeNode = attributes.getNamedItem("start"); int start = Integer.decode(attributeNode.getNodeValue()).intValue(); attributeNode = attributes.getNamedItem("end"); int end = Integer.decode(attributeNode.getNodeValue()).intValue(); DefaultStyledDocument doc = (DefaultStyledDocument) getDocument(); for (int i = 0; i < attributes.getLength(); i++) { attributeNode = attributes.item(i); String nodeName = attributeNode.getNodeName(); Object nodeValue = attributeNode.getNodeValue(); if (nodeName.equals("start")) continue; if (nodeName.equals("end")) continue; styleMethods.set(contentStyle, nodeName, nodeValue); } try { int length = end - start; if ((length > 0) && (end <= doc.getLength())) { String text = doc.getText(start, length); doc.replace(start, length, text, contentStyle); doc.setParagraphAttributes(start, length, paragraphStyle, false); } } catch (DOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } } protected void readIconElement(Node node) { NamedNodeMap xmlAttributes = node.getAttributes(); Node attributeNode = xmlAttributes.getNamedItem("href"); String urlString = attributeNode.getNodeValue(); attributeNode = xmlAttributes.getNamedItem("start"); int start = Integer.decode(attributeNode.getNodeValue()).intValue(); DefaultStyledDocument doc = (DefaultStyledDocument) getDocument(); try { URL iconURL = new URL(urlString); doc.remove(start, 1); int dot = getCaret().getDot(); getCaret().setDot(start); insertIcon(iconURL); getCaret().setDot(dot); } catch (MalformedURLException e) { } catch (BadLocationException e) { } } protected void readComponentElement(Node node) { NamedNodeMap xmlAttributes = node.getAttributes(); Node attributeNode = xmlAttributes.getNamedItem("href"); String urlString = attributeNode.getNodeValue(); attributeNode = xmlAttributes.getNamedItem("start"); int start = Integer.decode(attributeNode.getNodeValue()).intValue(); locationMap.put(urlString, new Integer(start)); } public void readTree(Document document, Node node) { if (node instanceof CDATASection) { try { String text = node.getNodeValue(); StringReader reader = new StringReader(text); super.read(reader, null); } catch (Exception e) { } } else { if (node.getNodeName().equals(AbstractDocument.SectionElementName)) { readDocumentElement(node, sectionStyle); } else if (node.getNodeName().equals(AbstractDocument.ParagraphElementName)) { readDocumentElement(node, paragraphStyle); } else if (node.getNodeName().equals(AbstractDocument.ContentElementName)) { readContentElement(node); } else if (node.getNodeName().equals("icon")) { readIconElement(node); } else if (node.getNodeName().equals("component")) { readComponentElement(node); } NodeList list = node.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { node = list.item(i); readTree(document, node); } } } public Selectable findSelectable(MouseEvent event, int x, int y) { return (Selectable) event.getSource(); } public Selectable getActiveObject() { return null; } public Point getOffset() { return null; } public void select(Selectable selectable, boolean multiple) { selectable.setSelected(true); } public void deselect() { } public void dragAction(int dx, int dy, Selectable selectable) { } public void dragActionDone(Selectable selectable) { } public void caretUpdate(CaretEvent e) { if (selectionManager != null) { Selectable selected = selectionManager.getSelectedObject(); if (selected != null) selected.setSelected(false); } } } }