package com.applang.components;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FilenameFilter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Observable;
import java.util.Observer;
import java.util.TreeSet;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ButtonModel;
import javax.swing.ComboBoxModel;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.ElementIterator;
import javax.swing.text.StyleConstants;
import javax.swing.text.html.FormSubmitEvent;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.Option;
import static com.applang.SwingUtil.*;
import static com.applang.Util.*;
import static com.applang.Util2.*;
import com.applang.SwingUtil.Behavior;
import com.applang.Util2;
import com.applang.berichtsheft.BerichtsheftApp;
public class FormEditor extends JSplitPane
{
public static void main(String[] args) throws Exception {
final String inputPath = param(BerichtsheftApp.odtVorlagePath("Tagesberichte"), 0, args);
final String outputPath = param(BerichtsheftApp.odtDokumentPath("Tagesberichte"), 1, args);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
perform(inputPath, outputPath, false);
}
});
}
public static boolean perform(final String inputPath, final String outputPath, final boolean deadline, Object... params) {
try {
BerichtsheftApp.loadSettings();
final Job<File> finish = new Job<File>() {
public void perform(File _content, Object[] params) throws Exception {
_content.delete();
BerichtsheftApp.manipContent(-1, inputPath, outputPath, null);
Settings.save();
}
};
boolean ok = BerichtsheftApp.manipContent(1, inputPath, outputPath,
new Job<File>() {
public void perform(final File content, final Object[] params) throws Exception {
inputDir = content.getParentFile();
final File _content = new File(inputDir, "_content.xml");
content.renameTo(_content);
if (!generateMask(_content.getCanonicalPath()))
return;
final FormEditor formEditor = new FormEditor();
showFrame(null,
"Layout manipulation",
new UIFunction() {
public Component[] apply(Component comp, Object[] parms) {
JFrame frame = (JFrame)comp;
Container contentPane = frame.getContentPane();
northToolBar(contentPane);
southStatusBar(contentPane);
contentPane.add(formEditor, BorderLayout.CENTER);
return null;
}
},
new UIFunction() {
public Component[] apply(Component comp, Object[] parms) {
JFrame frame = (JFrame)comp;
Bounds.load(frame, "frame", frame.getTitle());
formEditor.setDivider();
if (isAvailable(0, params)) {
try {
Job<FormEditor> job = param(null, 0, params);
job.perform(formEditor, null);
} catch (Exception e) {
handleException(e);
}
}
return null;
}
},
new UIFunction() {
public Component[] apply(Component comp, Object[] parms) {
JFrame frame = (JFrame)comp;
Bounds.save(frame, "frame", frame.getTitle());
unmask(_content.getPath(), content.getPath());
try {
if (!deadline)
finish.perform(_content, null);
} catch (Exception e) {
handleException(e);
}
return null;
}
},
deadline ? Behavior.TIMEOUT : 0);
if (deadline) {
if (isAvailable(1, params)) {
Job<Void> job = param(null, 1, params);
job.perform(null, new Object[] {_content.getPath(), content.getPath()});
}
try {
finish.perform(_content, null);
} catch (Exception e) {
handleException(e);
}
}
}
}, params);
return ok;
} catch (Exception e) {
handleException(e);
return false;
}
}
static void unmask(String inputPath, String outputPath) {
try {
String stylePath = BerichtsheftApp.applicationDataPath("Skripte/mask.xsl");
xmlTransform(inputPath, stylePath, outputPath, "mode", 2);
} catch (Exception e) {
handleException(e);
}
}
static File inputDir = null;
static boolean generateMask(String contentXml) {
try {
String stylePath = BerichtsheftApp.applicationDataPath("Skripte/mask.xsl");
String dummy = tempPath() + "/temp.html";
xmlTransform(contentXml, stylePath, dummy, "mode", 1);
File dir = tempDir(false, BerichtsheftApp.NAME);
pages = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.matches("page\\d+\\.html");
}
});
images = new Image[pages.length];
pageLayoutProperties = getElement("styles.xml",
"/document-styles" +
"/automatic-styles" +
"/page-layout" +
"/page-layout-properties");
return true;
} catch (Exception e) {
handleException(e);
return false;
}
}
static org.w3c.dom.Element getElement(String fileName, String xpath) {
File file = new File(inputDir, fileName);
if (fileExists(file)) {
org.w3c.dom.Document doc = xmlDocument(file);
org.w3c.dom.NodeList nodes = evaluateXPath(doc, xpath);
if (nodes.getLength() > 0)
return (org.w3c.dom.Element) nodes.item(0);
}
return null;
}
static Image loadImage(String path) {
try {
Image image = ImageIO.read(new File(path));
return image;
} catch (Exception e) {
handleException(e);
return null;
}
}
public static File[] pages = null;
public static org.w3c.dom.Element pageLayoutProperties = null;
public static Image[] images = null;
public FormEditor() {
try {
setupEditor();
setTopComponent(new JScrollPane(maskPanel));
setBottomComponent(new JScrollPane(editorPanel));
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
setDivider();
super.componentResized(e);
}
});
mappings = new ValMap[pages.length];
updateSplitComponents(page = 0, null);
} catch (Exception e) {
handleException(e);
}
}
private void setDivider() {
int dividerLocation = this.getWidth() / 2;
setDividerLocation(dividerLocation);
}
ValMap[] mappings = null;
int page;
public void updateSplitComponents(int page, String data) {
try {
if (pages.length > 0 && page < pages.length) {
updateMask(page, data);
String url = "file:" + pages[page].getPath();
editorPanel.setPage(url);
}
else
message("no mask data available");
} catch (Exception e) {
handleException(e);
}
}
MaskPanel maskPanel = new MaskPanel();
JEditorPane editorPanel = new JEditorPane();
private void setupEditor() {
editorPanel.setContentType("text/html");
editorPanel.setEditable(false);
HTMLEditorKit kit = (HTMLEditorKit)editorPanel.getEditorKit();
kit.setAutoFormSubmission(false);
editorPanel.addHyperlinkListener(new HyperlinkListener()
{
@Override
public void hyperlinkUpdate(HyperlinkEvent he)
{
URL url = he.getURL();
if (he instanceof FormSubmitEvent)
{
FormSubmitEvent fe = (FormSubmitEvent)he;
String data = fe.getData();
updateSplitComponents(page, data);
}
else if (he.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
String name = new File(url.getFile()).getName();
int page = toInt(0, findFirstIn(name, Pattern.compile("\\w+(\\d+)\\.\\w+")).group(1));
updateSplitComponents(page - 1, null);
}
}
});
}
private void forceReload() {
Document doc = editorPanel.getDocument();
doc.putProperty(Document.StreamDescriptionProperty, null);
}
@SuppressWarnings("unused")
private ValMap allMappings() {
ValMap map = vmap();
for (int p = 0; p < pages.length; p++) {
getPageData(p);
for (String key : mappings[p].keySet())
map.put(key, mappings[p].get(key));
}
return map;
}
private void updateMask(int page, String data) {
try {
getPageData(page);
if (notNullOrEmpty(data)) {
ValMap map = vmap();
String[] parts = data.split("&|=");
for (int i = 0; i < parts.length - 1; i+=2) {
String key = URLDecoder.decode(parts[i], "UTF-8");
String value = URLDecoder.decode(parts[i+1], "UTF-8");
map.put(key, value);
}
boolean change = false;
int action = 0;
for (String key : map.keySet()) {
String value = map.get(key).toString();
if (key.startsWith("control") && value.toLowerCase().equals("on")) {
change |= updateMapping(page, key, "x", map);
change |= updateMapping(page, key, "y", map);
change |= updateMapping(page, key, "width", map);
change |= updateMapping(page, key, "height", map);
}
else if (key.startsWith("action"))
action = toInt(0, findFirstIn(key, Pattern.compile("\\d+")).group());
}
switch (action) {
case 1:
if (change) {
putPageData(page);
forceReload();
}
break;
}
}
this.page = page;
maskPanel.update(null, null);
} catch (Exception e) {
handleException(e);
}
}
private boolean updateMapping(int page, String key1, String key2, ValMap map) {
boolean retval = false;
Object value = map.get(key2);
if (value != null) {
float val = toFloat(Float.NaN, value.toString());
if (!Float.isNaN(val)) {
mappings[page].put(key1 + "_" + key2, val);
retval = true;
}
}
return retval;
}
private void putPageData(int page) {
org.w3c.dom.Document doc = map2page(mappings[page], page, false);
if (doc != null)
xmlNodeToFile(doc, true, pages[page]);
}
public static org.w3c.dom.Document map2page(ValMap map, int page, boolean reverse) {
org.w3c.dom.Document doc = xmlDocument(pages[page]);
if (doc == null)
return null;
org.w3c.dom.NodeList nodes = evaluateXPath(doc, "//table[@id='controls']");
if (nodes.getLength() > 0) {
nodes = evaluateXPath(nodes.item(0), ".//*[@name]");
for (int i = 0; i < nodes.getLength(); i++) {
org.w3c.dom.Element node = (org.w3c.dom.Element)nodes.item(i);
String key = node.getAttribute("name");
if (key.contains("_")) {
boolean inputTag = key.endsWith("image");
if (reverse) {
String value = inputTag ? node.getAttribute("value") : node.getTextContent();
map.put(key, value);
if (inputTag)
images[page] = loadImage(new File(inputDir, value).getPath());
} else {
Object value = map.get(key);
if (value != null)
if (inputTag)
node.setAttribute("value", value.toString());
else
node.setTextContent(value.toString());
}
}
}
}
return doc;
}
private void getPageData(int page) {
mappings[page] = vmap();
map2page(mappings[page], page, true);
}
@SuppressWarnings("unused")
private String getFormData() {
StringBuilder buffer = new StringBuilder();
HTMLDocument doc = (HTMLDocument) editorPanel.getDocument();
Element table = doc.getElement("controls");
if (table != null) {
ElementIterator it = new ElementIterator(table);
Element next;
while ((next = it.next()) != null) {
if (next.isLeaf())
loadElementDataIntoBuffer(next, buffer);
}
}
return buffer.toString();
}
// taken from javax.swing.text.html.FormView class
private void loadElementDataIntoBuffer(Element elem, StringBuilder buffer) {
AttributeSet attr = elem.getAttributes();
String name = (String)attr.getAttribute(HTML.Attribute.NAME);
if (name == null) {
return;
}
String value = null;
HTML.Tag tag = (HTML.Tag)elem.getAttributes().getAttribute
(StyleConstants.NameAttribute);
if (tag == HTML.Tag.INPUT) {
value = getInputElementData(attr);
} else if (tag == HTML.Tag.TEXTAREA) {
value = getTextAreaData(attr);
} else if (tag == HTML.Tag.SELECT) {
loadSelectData(attr, buffer);
}
if (name != null && value != null) {
appendBuffer(buffer, name, value);
}
}
@SuppressWarnings("deprecation")
private void appendBuffer(StringBuilder buffer, String name, String value) {
if (buffer.length() > 0) {
buffer.append('&');
}
String encodedName = URLEncoder.encode(name);
buffer.append(encodedName);
buffer.append('=');
String encodedValue = URLEncoder.encode(value);
buffer.append(encodedValue);
}
private String getInputElementData(AttributeSet attr) {
Object model = attr.getAttribute(StyleConstants.ModelAttribute);
String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
String value = null;
if (type.equals("text") || type.equals("password")) {
Document doc = (Document)model;
try {
value = doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
value = null;
}
} else if (type.equals("submit") || type.equals("hidden")) {
value = (String) attr.getAttribute(HTML.Attribute.VALUE);
if (value == null) {
value = "";
}
} else if (type.equals("radio") || type.equals("checkbox")) {
ButtonModel m = (ButtonModel)model;
if (m.isSelected()) {
value = (String) attr.getAttribute(HTML.Attribute.VALUE);
if (value == null) {
value = "on";
}
}
} else if (type.equals("file")) {
Document doc = (Document)model;
String path;
try {
path = doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
path = null;
}
if (path != null && path.length() > 0) {
value = path;
}
}
return value;
}
private String getTextAreaData(AttributeSet attr) {
Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
try {
return doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
return null;
}
}
@SuppressWarnings("rawtypes")
private void loadSelectData(AttributeSet attr, StringBuilder buffer) {
String name = (String)attr.getAttribute(HTML.Attribute.NAME);
if (name == null) {
return;
}
Object m = attr.getAttribute(StyleConstants.ModelAttribute);
/*if (m instanceof OptionListModel) {
OptionListModel model = (OptionListModel)m;
for (int i = 0; i < model.getSize(); i++) {
if (model.isSelectedIndex(i)) {
Option option = (Option) model.getElementAt(i);
appendBuffer(buffer, name, option.getValue());
}
}
} else */if (m instanceof ComboBoxModel) {
ComboBoxModel model = (ComboBoxModel)m;
Option option = (Option)model.getSelectedItem();
if (option != null) {
appendBuffer(buffer, name, option.getValue());
}
}
}
private void createMaskPanelPopupMenu(final MaskPanel maskPanel) {
final String[] factors = strings("offsetX","offsetY","scaleX","scaleY");
final JPanel pnl = new JPanel();
final Observer observer = new Observer() {
@Override
public void update(Observable o, Object arg) {
for (int i = 0, j = 0; i < factors.length; i++, j=i%2) {
JTextField field = findFirstComponent(pnl, factors[i]);
if (o instanceof MaskPanel.Scale && factors[i].startsWith("scale")) {
MaskPanel.Scale scale = (MaskPanel.Scale) o;
field.setText("" + round(scale.getDim(j), 3));
}
if (o instanceof MaskPanel.Offset && factors[i].startsWith("offset")) {
MaskPanel.Offset offset = (MaskPanel.Offset) o;
field.setText("" + round(offset.getCoord(j), 3));
}
}
}
};
maskPanel.addMouseListener(newPopupAdapter(
new Object[] {"Mask fitting ...",
new ActionListener() {
public void actionPerformed(ActionEvent ae) {
showDialog(maskPanel, maskPanel,
"Mask fitting",
new UIFunction() {
public Component[] apply(Component dlg, Object[] parms) {
pnl.setLayout(new BoxLayout(pnl, BoxLayout.PAGE_AXIS));
Dimension fieldSize = new Dimension(160,20);
for (int i = 0; i < factors.length; i++) {
if (findFirstComponent(pnl, factors[i]) == null) {
final JTextField field = new JTextField();
field.setName(factors[i]);
field.setPreferredSize(fieldSize);
field.setHorizontalAlignment(JTextField.CENTER);
JLabel label = new JLabel(factors[i]);
label.setLabelFor(field);
label.setPreferredSize(fieldSize);
label.setHorizontalAlignment(SwingConstants.CENTER);
pnl.add(label);
label.setAlignmentX(Component.CENTER_ALIGNMENT);
pnl.add(field);
field.setAlignmentX(Component.CENTER_ALIGNMENT);
field.addActionListener(new ActionListener() {
public void actionPerformed(
ActionEvent e) {
String name = field
.getName();
int i = asList(
factors).indexOf(
name) % 2;
String text = field
.getText();
if (name.startsWith("offset")) {
double d = maskPanel.offset
.getCoord(i);
d = toDouble(d,
text);
maskPanel.offset
.setCoord(i, d);
}
if (name.startsWith("scale")) {
double d = maskPanel.scale
.getDim(i);
d = toDouble(d,
text);
maskPanel.scale.setDim(
i, d);
}
}
});
}
}
return components(pnl);
}
},
new UIFunction() {
public Component[] apply(Component dlg, Object[] parms) {
maskPanel.scale.addObserver(observer);
observer.update(maskPanel.scale, null);
maskPanel.offset.addObserver(observer);
observer.update(maskPanel.offset, null);
return null;
}
},
new UIFunction() {
public Component[] apply(Component dlg, Object[] parms) {
maskPanel.scale.deleteObserver(observer);
maskPanel.offset.deleteObserver(observer);
return null;
}
},
Behavior.NONE);
}
}, "scale", "change scale factors of the mask"}
));
}
public class MaskPanel extends JPanel implements Observer
{
public class Scale extends Observable
{
public void setDim(int i, double d) {
double[] dd = new double[]{dim.getWidth(),dim.getHeight()};
dd[i] = d;
dim.setSize(dd[0], dd[1]);
setChanged();
notifyObservers(this);
}
public double getDim(int i) {
double[] dd = new double[]{dim.getWidth(),dim.getHeight()};
return dd[i];
}
public Dimension2D dim = new Dimension2D() {
double width, height;
@Override
public void setSize(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getWidth() {
return width;
}
@Override
public double getHeight() {
return height;
}
};
public String toString() {
Writer writer = write(new StringWriter(), "[");
writer = write_assoc(writer, "width", dim.getWidth());
writer = write_assoc(writer, "height", dim.getHeight(), 1);
return write(writer, "]").toString();
}
}
public class Offset extends Observable
{
public void setCoord(int i, double d) {
double[] cc = new double[]{point.getX(),point.getY()};
cc[i] = d;
point.setLocation(cc[0], cc[1]);
setChanged();
notifyObservers(this);
}
public double getCoord(int i) {
double[] cc = new double[]{point.getX(),point.getY()};
return cc[i];
}
public Point2D point = new Point2D() {
double x, y;
@Override
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public double getY() {
return y;
}
@Override
public double getX() {
return x;
}
};
public String toString() {
Writer writer = write(new StringWriter(), "[");
writer = write_assoc(writer, "x", point.getX());
writer = write_assoc(writer, "y", point.getY(), 1);
return write(writer, "]").toString();
}
}
public Scale scale;
public Offset offset;
class MouseHandler extends MouseAdapter {
private int offsetX;
private int offsetY;
public void mousePressed(MouseEvent e) {
offsetX = e.getX();
offsetY = e.getY();
}
public void mouseDragged(MouseEvent e) {
int deltaX = e.getX() - offsetX;
int deltaY = e.getY() - offsetY;
offsetX += deltaX;
offsetY += deltaY;
offset.setCoord(0, offset.getCoord(0) + deltaX);
offset.setCoord(1, offset.getCoord(1) + deltaY);
}
public void mouseWheelMoved(MouseWheelEvent e) {
if(e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
double delta = .005 * e.getWheelRotation();
if (e.isShiftDown())
scale.setDim(1, Math.max(0.00001, scale.getDim(1) + delta));
if (e.isControlDown())
scale.setDim(0, Math.max(0.00001, scale.getDim(0) + delta));
}
}
}
public MaskPanel() {
setBackground(Color.white);
offset = new Offset();
offset.point.setLocation(0.0, 0.0);
offset.addObserver(this);
scale = new Scale();
scale.dim.setSize(1.0, 1.0);
scale.addObserver(this);
MouseHandler mouseHandler = new MouseHandler();
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
addMouseWheelListener(mouseHandler);
createMaskPanelPopupMenu(this);
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
resizeMask();
update(null, null);
}
});
}
@Override
public void update(Observable o, Object arg) {
// println("update offset : %s\tscale : %s", offset, scale);
repaint();
}
// 72 units in user space equals 1 inch in device space
private static final int device2userSpace = 72;
private Double pageLayout(String name) {
if (pageLayoutProperties == null)
return null;
String attribute = pageLayoutProperties.getAttribute(name);
return toDouble(null, stripUnits(attribute));
}
Double[] page_dims = new Double[] {
pageLayout("fo:page-width"),
pageLayout("fo:page-height"),
};
Double[] margins = new Double[] {
pageLayout("fo:margin-left"),
pageLayout("fo:margin-top"),
};
public double getDim(int i) {
double[] dd = new double[]{this.getWidth(),this.getHeight()};
return dd[i];
}
private void resizeMask() {
for (int i = 0; i < 2; i++) {
double user = this.getDim(i);
Double dim = page_dims[i];
if (dim == null) {
Object frameItem = frameItem(page, i==0 ? "width" : "height");
if (frameItem != null)
dim = toDouble(
new Double(1 / device2userSpace),
frameItem.toString());
}
scale.setDim(i, user / device2userSpace / dim);
offset.setCoord(i, margins[i] == null ? 0 : device2userSpace * margins[i] * scale.getDim(i));
}
}
private Object frameItem(int page, String item) {
return mappings[page].get("frame" + (page+1) + "_" + item);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setColor(Color.black);
ValMap map = mappings[page];
if (map == null)
return;
if (isAvailable(page, images))
g2.drawImage(images[page], 0, 0, this.getWidth(), this.getHeight(), null);
AffineTransform tx = new AffineTransform();
tx.translate(offset.point.getX(), offset.point.getY());
tx.scale(scale.dim.getWidth(), scale.dim.getHeight());
g2.setTransform(tx);
TreeSet<String> controls = new TreeSet<String>();
for (String key : map.keySet()) {
int underscore = key.indexOf('_');
if (underscore > 0 && key.startsWith("control"))
controls.add(key.substring(0, underscore));
}
for (String control : controls) {
String _x = map.get(control + "_x").toString();
String _y = map.get(control + "_y").toString();
String _width = map.get(control + "_width").toString();
String _height = map.get(control + "_height").toString();
float x = device2userSpace * toFloat(Float.NaN, _x);
float y = device2userSpace * toFloat(Float.NaN, _y);
float width = device2userSpace * toFloat(Float.NaN, _width);
float height = device2userSpace * toFloat(Float.NaN, _height);
Rectangle2D.Double rect = new Rectangle2D.Double(x, y, width, height);
g2.draw(rect);
g2.drawString(control, x, y + height);
}
}
}
}